From 05ab9639518fb17dac764be8de077769e85eb9dd Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 20 Sep 2026 07:51:08 +0200 Subject: [PATCH 1/2] fix(issues): the preview must not ask the tracker for a repository context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The composer's preview did not fail a call — it killed the Gitea process, and with it the git server and the container registry. `POST /api/v1/markdown` with `{"Mode": "gfm", "Context": ""}` makes Gitea resolve every hex-shaped word as a commit link, and on the API's render path the commit checker it asks is nil: panic: runtime error: invalid memory address or nil pointer dereference renderhelper.(*commitChecker).IsCommitIDExisting(0x0) <- markup.hashCurrentPatternProcessor (html_commit.go:217) Every report carries hex — a tab id, a request id — so this was not an edge case but the only case: the preview answered "the tracker isn't answering" every time, because the tracker had just died. Measured on throwaway instances, not argued. Same body, same call: gfm + Context + "5645b1c0ae01" -> 1.26.4 CRASH, 1.27.1 CRASH, 1.27.3 200 gfm + Context, no hex -> 200 gfm, no Context -> 200, and the HTML keeps its tables, task lists and chroma-highlighted code blocks Upstream fixed it in 1.27.2 ("fix: avoid markup render panic", #38698), but a self-hoster runs whatever version they run, so the payload is what gets fixed. Sending was never affected: the created issue's own page renders the same body fine (measured) — a real repo context has a real commit checker. The only loss is that a hash or a `#123` is not yet a link in the preview; the issue page links it. The test pins the payload dict, because the defect is in what we send. --- backend/app/issues/gitea.py | 21 +++++++++++++++++---- backend/tests/test_debug_sessions.py | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/backend/app/issues/gitea.py b/backend/app/issues/gitea.py index aeb24492..6d462296 100644 --- a/backend/app/issues/gitea.py +++ b/backend/app/issues/gitea.py @@ -3,7 +3,7 @@ before a line of this was written (plans/e16-debug-issue.md, S0). Configured by an admin (sysconfig `issues_gitea_url` / `_repo` / `_token`, the token encrypted at rest). The repository must be PRIVATE: an issue carries its reporter's log — page titles, filter -values, at Teljes what they typed into search — and a public repository would publish it. That is +values, at Teljes (TRACE) what they typed into search — and a public repository would publish it. That is checked on every send, not only in the admin's "test connection", because a repository can be made public after the fact. @@ -154,9 +154,22 @@ def check(db: Session) -> dict: def preview(db: Session, text: str) -> str: - """The body as Gitea itself will render it (its markdown flavour, its sanitiser).""" - with _client(_settings(db)) as (client, repo): - r = _call(client, "POST", "/markdown", json={"Text": text, "Mode": "gfm", "Context": repo}) + """The body as Gitea itself will render it (its markdown flavour, its sanitiser). + + **No `Context`, deliberately — a repository context here does not fail the call, it kills the + tracker.** With one, Gitea's post-processor turns every hex-shaped word into a commit link, and + on the API's render path the commit checker it asks is nil: the whole Gitea process segfaults + and restarts, taking the git server (and, here, the container registry) down with it. Every + report carries hex — a tab id, a request id — so this was not an edge case but the only case. + Measured on throwaway instances (2026-09-20): `{"Mode": "gfm", "Context": ""}` with + `5645b1c0ae01` in the text crashes 1.26.4 and 1.27.1 and is fixed in 1.27.3 — but a self-hoster + runs whatever version they run, so the payload stays safe here rather than relying on theirs. + Dropping `Context` is not a downgrade of the preview: measured against the same body, the HTML + keeps its tables, its task lists and its highlighted code blocks. It only stops resolving + repo-relative links (a hash, a `#123`), which the issue page itself still resolves. + """ + with _client(_settings(db)) as (client, _repo): + r = _call(client, "POST", "/markdown", json={"Text": text, "Mode": "gfm"}) return r.text diff --git a/backend/tests/test_debug_sessions.py b/backend/tests/test_debug_sessions.py index 5c1730b0..6a791922 100644 --- a/backend/tests/test_debug_sessions.py +++ b/backend/tests/test_debug_sessions.py @@ -786,6 +786,7 @@ class FakeGitea: self.redirect = False # an http:// address behind a proxy that sends it to https self.garbage_asset = False # a 2xx that is not the API's answer (a proxy's page) self.patched_body: str | None = None + self.markdown_payloads: list[dict] = [] # what the preview asked the tracker to render def handler(self, request: httpx.Request) -> httpx.Response: path = request.url.path.removeprefix("/api/v1") @@ -826,6 +827,7 @@ class FakeGitea: self.patched_body = json.loads(request.content)["body"] return httpx.Response(201, json={"number": 7}) if request.method == "POST" and path == "/markdown": + self.markdown_payloads.append(json.loads(request.content)) return httpx.Response(200, text="

Steps

") return httpx.Response(404, json={"message": "not found"}) @@ -1180,6 +1182,20 @@ def test_the_preview_is_the_trackers_own_rendering(gitea_fake, db, make_user, as assert stranger.post(f"/api/debug-sessions/{sid}/issue").status_code == 404 +def test_the_preview_never_asks_the_tracker_for_a_repository_context( + gitea_fake, db, make_user, as_user +): + """A `Context` makes Gitea hunt for commit links, and on the API's render path that hunt + dereferences a nil checker: the tracker's whole process segfaults. Every report carries a + hex-shaped id, so the payload — not the text — is what has to stay safe (measured against + 1.26.4 / 1.27.1, fixed upstream in 1.27.3; a self-hoster's version is not ours to pick).""" + owner = as_user(make_user()) + sid = _start(owner)["id"] + text = "| tab | 5645b1c0ae01 |" + assert owner.post(f"/api/debug-sessions/{sid}/preview", json={"text": text}).status_code == 200 + assert gitea_fake.markdown_payloads == [{"Text": text, "Mode": "gfm"}] + + def test_the_admin_connection_test_names_what_is_wrong(gitea_fake, db, make_user, as_user): admin = as_user(make_user(role="admin")) r = admin.post("/api/admin/config/test-issues") From 0e9839e73d8bf77178de951960b56f405165c1ce Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 20 Sep 2026 07:51:16 +0200 Subject: [PATCH 2/2] =?UTF-8?q?chore(release):=200.93.1=20=E2=80=94=20the?= =?UTF-8?q?=20report=20preview=20works=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- frontend/src/lib/releaseNotes.ts | 8 ++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b778a5b..90c5ed72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ edit the release notes and re-run the generator. Upgrading, pinning a version and rolling back: [docs/self-hosting.md](docs/self-hosting.md#upgrading). +## 0.93.1 — 2026-09-20 + +The report composer's preview works again. + +### Fixes + +- Looking at a report before sending it said "the issue tracker isn't answering" — every time, for everyone. The preview asked the tracker to resolve links against the repository, and a report always carries something that looks like a commit hash: an id of the tab, of a request. On a tracker older than Gitea 1.27.2 that request does not fail, it stops the tracker — so the preview now asks for the rendering without the repository, which shows the same tables, lists and code, and can no longer take a tracker down. Sending a report was never affected. + ## 0.93.0 — 2026-09-20 A way to report a fault from inside the app: record what you did, mark the steps, attach screenshots — and the app writes the report for you, with the logs of exactly that session attached. diff --git a/VERSION b/VERSION index 9188543e..a16a6df5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.93.0 +0.93.1 diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index 44402ca4..5757d9ab 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -14,6 +14,14 @@ export interface ReleaseEntry { } export const RELEASE_NOTES: ReleaseEntry[] = [ + { + version: "0.93.1", + date: "2026-09-20", + summary: "The report composer's preview works again.", + fixes: [ + 'Looking at a report before sending it said "the issue tracker isn\'t answering" — every time, for everyone. The preview asked the tracker to resolve links against the repository, and a report always carries something that looks like a commit hash: an id of the tab, of a request. On a tracker older than Gitea 1.27.2 that request does not fail, it stops the tracker — so the preview now asks for the rendering without the repository, which shows the same tables, lists and code, and can no longer take a tracker down. Sending a report was never affected.', + ], + }, { version: "0.93.0", date: "2026-09-20",