HOMELAB-1736: fix(forgejo): CI status/failure tools — robust endpoint fallback #48

Merged
aaron merged 4 commits from plane/HOMELAB-1736-forgejo-ci into main 2026-09-30 03:39:52 +00:00
Owner

What

Lands the forgejo-ci extension (forgejo_ci_status, forgejo_ci_failures) into the repo and fixes the actions/tasks HTTP 500 that blinded the landing pipeline.

Fixes HOMELAB-1736.

2 commits, reviewable in order:

  1. chore(forgejo-ci): salvage the unlanded HOMELAB-1639 extension into the repo — byte-for-byte (md5-identical) copy of the working tree that lives only at ~/.pi/agent/extensions/forgejo-ci/. HOMELAB-1639 (2026-09-22) delivered the extension but recorded "Nothing outside ~/.pi/agent/ ... was modified", so it never landed here. The copy is verbatim so commit 2 is a real diff against known state.
  2. fix(forgejo-ci): page-safe CI status/failure tools with endpoint fallback — the fix.

Why the tools were broken (root cause, reproduced)

GET /repos/{o}/{r}/actions/tasks honours limit only when page is also sent:

  • routers/api/v1/repo/action.go:588 → db.FindAndCount[ActionTask]{ListOptions: utils.GetListOptions(ctx)}
  • routers/api/v1/utils/page.go → ListOptions{Page: ctx.FormInt("page"), PageSize: ToCorrectPageSize(limit)} — Page=0 when the client omits it
  • models/db/list.go:186 FindAndCount → if !IsListAll() && pageSize > 0 && page >= 1 { sess.Limit(...) } — the page >= 1 guard drops the LIMIT

Measured against the running instance (14.0.3+gitea-1.22.0), same auth the extension uses (Authorization: token <PAT>):

repo tasks ?limit=5 (no page) ?page=1&limit=5
Sendrast/sendrast-web 893 200 — limit ignored, 893 rows / 443 KB 200, 5 rows, 0.23 s
Homelab/lab-director 1657 500 after 0.6 s 200, 0.32 s
Homelab/infra-core 2274 500 after 6.3 s (10/10 runs) 200, 0.26 s
Homelab/prod 3056 500 after 7.1 s 200, 0.22 s

The 500 body is {"message":""} because Forgejo blanks 500 bodies for non-admin callers (services/context/api.go:179-198) — that is why earlier investigators could not see the cause. /actions/runs has the same limit-dropping behaviour (?limit=100 returned all 401 runs); it only escaped the 500 because its table is smaller. The extension called ?limit=100/?limit=200 with no page on both endpoints, so every Homelab repo 500'd while small repos (sendrast-web) worked — matching the "some repos 500, some don't" reports.

Reproduced with the deployed tool before the fix: forgejo_ci_status Homelab/infra-core → Forgejo API 500 on /repos/Homelab/infra-core/actions/tasks?limit=100.

The fix

  • Api.paged() always sends page+limit, walks pages, clamps an over-long page defensively (protects against the upstream bug recurring), and covers the whole feed where it must (the run list has NO defined ordering, so a partial window cannot be trusted).
  • Run lookup: a run index uses the server-side run_number filter → one exact request. PR / branch / sha refs cannot use that filter, so they walk the whole /actions/runs feed (bounded by total_count) and take the newest match, ranked by match precision first (an exact prettyref/PR match outranks a title substring). #N is read as a PR number first, then as a run index.

Delta review (2nd pass) found and fixed three further defects in the first fix, all independently reproduced against the live server: the no-query listing sampled 150 of 492 unordered runs and reported an old run as newest (a regression — the pre-fix tool returned [492,491,490,489,488]); PR/branch/sha lookups stopped at the first page containing any match (#147 -> 458 instead of the newest 468); and the tasks paging stopped with <= exactly at a run split across a page boundary (run 441 -> only e2e, reported with degraded: []). See the review/landing comments below.
Follow-up (c9ade35, 3rd pass): the whole-feed newest-match change let a newer run on a different branch win a branch lookup when its title merely contained the branch name. Matches are now ranked by precision (exact prettyref / exact PR number tier, then title-substring) before recency. Live: Homelab/prod query live now returns 1046 (the newest prettyref === "live" run) instead of 1163. The same commit surfaces a partial task feed completed from the run page in degraded, normalises the union de-dup key, and reports runsTruncated when the maxPages cap is hit.

  • Degradation ladder: actions/tasks → web run page → commits/{sha}/status contexts → run-level status. A job-level failure can no longer kill the tool; the degraded source is reported (details.degraded + a note: line).
  • Durations/indexes now come from the run page (authoritative); task updated_at (bumped hours after a job ends) is only a fallback, clamped to the run duration.
  • Log handling: a missing attempt answers 500 (resource does not exist), not 404; cleaned-up/never-started logs are named; a failed job whose log has no failure marker now yields a bounded tail excerpt instead of "log has no failure marker" (a gap noted in .rpiv/artifacts/work/epic0-contracts/review-send139.md); excerpts anchor on runner/cancellation markers so a cancelled log no longer dumps 30 lines of passing specs.
  • Response contract preserved: same text layout, details is a superset of the previous keys (owner/repo/run/runId/failedJobs; runs[].jobs[].name/status).

Evidence

Live replay (real Forgejo, no fixtures) — FORGEJO_LIVE=1 npx vitest run tests/live.test.ts, 11/11 pass:

forgejo_ci_status Homelab/infra-core 1481     # used to 500
run #1481  c7a74c7d4a  live  [pass]  ... pi sandbox image → Harbor' (#519)
    ✔ build-and-push success     1m41s        # tasks source, degraded: []

forgejo_ci_status Sendrast/sendrast-web 489
run #489  bacdfc93bf  main  [FAIL]  ... (#155) ...
    ⛔ e2e          cancelled   9m49s  (1 failed)
    ✔ test         success     5m49s

forgejo_ci_failures Sendrast/sendrast-web 489
  ✘ job e2e          index 1    9m49s  failed attempts: #1:cancelled
      ── attempt #1 (task 14052) ──
      error excerpt:
        ⚙️ [runner]: context canceled ... 🏁  Job failed / context canceled

forgejo_ci_failures Homelab/infra-core 1226
  ✘ job build-and-push index 0      43s  failed attempts: (failure via run-page)
      error excerpt:
        sed: can't read core/manifests/apps/docs-site/deployment.yaml: No such file or directory
        ⚙️ [runner]: RUN exit status 2
        🏁  Job failed

AC replay from the ticket: sendrast-web #489 / #490 (cancelled e2e jobs → excerpt returned) and #492 ([pass]), plus infra-core run 1481. Green runs report degraded: [] and jobsSource: tasks.

Durations now match the Forgejo UI (5m49s / 9m49s) instead of the previous 2591m52s/43h35m49s artefacts of task updated_at.

Tests / CI

  • forgejo-ci/package.json + tsconfig.json + package-lock.json; new CI job forgejo-ci (npm ci → npm run typecheck → npm test).
  • 52 tests against a fake Forgejo route table (no network): paging URL shape (page+limit always present), pagination/truncation/clamp, run_number fast path + candidate fallback, branch/PR precision ranking (an exact ref beats a newer title mention), degradation paths (tasks 500 → run page / commit-status / run-level), partial-task-feed → run-page union surfaced in degraded, case/whitespace-insensitive job-union de-dup, maxPages truncation surfacing, log unavailability (404, resource does not exist, cleaned up, never started), excerpt anchoring, duration parsing/clamping.
  • tests/live.test.ts is opt-in (FORGEJO_LIVE=1) and self-skips without it — CI has no Forgejo credentials.
  • npm test + npm run typecheck green locally, plus tsc --noUnusedLocals --noUnusedParameters clean.

Territory / drift note

  • Touched: forgejo-ci/** (new), docs/forgejo-ci.md (new), README.md (extension + config rows), .forgejo/workflows/ci.yaml (one job). No other extension touched.
  • The untracked ~/.pi/agent/extensions/forgejo-ci/ is untouched (md5s verified unchanged) for the HOMELAB-1740 custodian. Recommended reconciliation: delete it once this lands — it is the same 1639 source, now tracked and fixed. Note for 1740: its description guesses that dir is "the working tree of the HOMELAB-1736 fix"; it is not — it predates 1736 by a week and is the 1639 deliverable.
  • Upstream: this is a Forgejo 14.0.3 bug (FindAndCount dropping the LIMIT for page == 0). The client-side fix is in place; the upstream-shaped detail (endpoint, request shape, reproduction, source lines) is recorded on HOMELAB-1736 rather than left as a broken tool.
## What Lands the `forgejo-ci` extension (`forgejo_ci_status`, `forgejo_ci_failures`) into the repo and fixes the `actions/tasks` HTTP 500 that blinded the landing pipeline. Fixes HOMELAB-1736. 2 commits, reviewable in order: 1. **`chore(forgejo-ci): salvage the unlanded HOMELAB-1639 extension into the repo`** — byte-for-byte (md5-identical) copy of the working tree that lives only at `~/.pi/agent/extensions/forgejo-ci/`. HOMELAB-1639 (2026-09-22) delivered the extension but recorded "Nothing outside `~/.pi/agent/` ... was modified", so it never landed here. The copy is verbatim so commit 2 is a real diff against known state. 2. **`fix(forgejo-ci): page-safe CI status/failure tools with endpoint fallback`** — the fix. ## Why the tools were broken (root cause, reproduced) `GET /repos/{o}/{r}/actions/tasks` honours `limit` **only when `page` is also sent**: - `routers/api/v1/repo/action.go:588` → `db.FindAndCount[ActionTask]{ListOptions: utils.GetListOptions(ctx)}` - `routers/api/v1/utils/page.go` → `ListOptions{Page: ctx.FormInt("page"), PageSize: ToCorrectPageSize(limit)}` — `Page=0` when the client omits it - `models/db/list.go:186` `FindAndCount` → `if !IsListAll() && pageSize > 0 && page >= 1 { sess.Limit(...) }` — the `page >= 1` guard **drops the LIMIT** Measured against the running instance (14.0.3+gitea-1.22.0), same auth the extension uses (`Authorization: token <PAT>`): | repo | tasks | `?limit=5` (no page) | `?page=1&limit=5` | |------|-------|----------------------|-------------------| | Sendrast/sendrast-web | 893 | 200 — **limit ignored, 893 rows / 443 KB** | 200, 5 rows, 0.23 s | | Homelab/lab-director | 1657 | **500** after 0.6 s | 200, 0.32 s | | Homelab/infra-core | 2274 | **500** after 6.3 s (10/10 runs) | 200, 0.26 s | | Homelab/prod | 3056 | **500** after 7.1 s | 200, 0.22 s | The 500 body is `{"message":""}` because Forgejo blanks 500 bodies for non-admin callers (`services/context/api.go:179-198`) — that is why earlier investigators could not see the cause. `/actions/runs` has the same limit-dropping behaviour (`?limit=100` returned all 401 runs); it only escaped the 500 because its table is smaller. The extension called `?limit=100`/`?limit=200` with no `page` on both endpoints, so **every Homelab repo 500'd** while small repos (sendrast-web) worked — matching the "some repos 500, some don't" reports. Reproduced with the deployed tool before the fix: `forgejo_ci_status Homelab/infra-core` → `Forgejo API 500 on /repos/Homelab/infra-core/actions/tasks?limit=100`. ## The fix - `Api.paged()` **always** sends `page`+`limit`, walks pages, clamps an over-long page defensively (protects against the upstream bug recurring), and covers the whole feed where it must (the run list has NO defined ordering, so a partial window cannot be trusted). - Run lookup: a run **index** uses the server-side `run_number` filter → **one exact request**. PR / branch / sha refs cannot use that filter, so they walk the whole `/actions/runs` feed (bounded by `total_count`) and take the **newest** match, ranked by match precision first (an exact `prettyref`/PR match outranks a title substring). `#N` is read as a PR number first, then as a run index. > **Delta review (2nd pass) found and fixed three further defects** in the first fix, all independently reproduced against the live server: the no-query listing sampled 150 of 492 unordered runs and reported an old run as newest (a *regression* — the pre-fix tool returned `[492,491,490,489,488]`); PR/branch/sha lookups stopped at the first page containing any match (`#147` -> 458 instead of the newest 468); and the tasks paging stopped with `<=` exactly at a run split across a page boundary (run 441 -> only `e2e`, reported with `degraded: []`). See the review/landing comments below. > **Follow-up (`c9ade35`, 3rd pass):** the whole-feed newest-match change let a newer run on a *different* branch win a branch lookup when its title merely contained the branch name. Matches are now ranked by precision (exact `prettyref` / exact PR number tier, then title-substring) before recency. Live: `Homelab/prod` query `live` now returns **1046** (the newest `prettyref === "live"` run) instead of 1163. The same commit surfaces a partial task feed completed from the run page in `degraded`, normalises the union de-dup key, and reports `runsTruncated` when the `maxPages` cap is hit. - **Degradation ladder:** `actions/tasks` → web run page → `commits/{sha}/status` contexts → run-level status. A job-level failure can no longer kill the tool; the degraded source is reported (`details.degraded` + a `note:` line). - Durations/indexes now come from the run page (authoritative); task `updated_at` (bumped hours after a job ends) is only a fallback, clamped to the run duration. - Log handling: a missing attempt answers **500** (`resource does not exist`), not 404; cleaned-up/never-started logs are named; a failed job whose log has no failure marker now yields a bounded tail excerpt instead of "log has no failure marker" (a gap noted in `.rpiv/artifacts/work/epic0-contracts/review-send139.md`); excerpts anchor on runner/cancellation markers so a cancelled log no longer dumps 30 lines of passing specs. - Response contract preserved: same text layout, `details` is a superset of the previous keys (`owner/repo/run/runId/failedJobs`; `runs[].jobs[].name/status`). ## Evidence Live replay (real Forgejo, no fixtures) — `FORGEJO_LIVE=1 npx vitest run tests/live.test.ts`, 11/11 pass: ``` forgejo_ci_status Homelab/infra-core 1481 # used to 500 run #1481 c7a74c7d4a live [pass] ... pi sandbox image → Harbor' (#519) ✔ build-and-push success 1m41s # tasks source, degraded: [] forgejo_ci_status Sendrast/sendrast-web 489 run #489 bacdfc93bf main [FAIL] ... (#155) ... ⛔ e2e cancelled 9m49s (1 failed) ✔ test success 5m49s forgejo_ci_failures Sendrast/sendrast-web 489 ✘ job e2e index 1 9m49s failed attempts: #1:cancelled ── attempt #1 (task 14052) ── error excerpt: ⚙️ [runner]: context canceled ... 🏁 Job failed / context canceled forgejo_ci_failures Homelab/infra-core 1226 ✘ job build-and-push index 0 43s failed attempts: (failure via run-page) error excerpt: sed: can't read core/manifests/apps/docs-site/deployment.yaml: No such file or directory ⚙️ [runner]: RUN exit status 2 🏁 Job failed ``` AC replay from the ticket: sendrast-web **#489 / #490** (cancelled e2e jobs → excerpt returned) and **#492** (`[pass]`), plus infra-core run **1481**. Green runs report `degraded: []` and `jobsSource: tasks`. Durations now match the Forgejo UI (`5m49s` / `9m49s`) instead of the previous `2591m52s`/`43h35m49s` artefacts of task `updated_at`. ## Tests / CI - `forgejo-ci/package.json` + `tsconfig.json` + `package-lock.json`; new CI job `forgejo-ci` (`npm ci` → `npm run typecheck` → `npm test`). - 52 tests against a fake Forgejo route table (no network): paging URL shape (`page`+`limit` always present), pagination/truncation/clamp, `run_number` fast path + candidate fallback, branch/PR precision ranking (an exact ref beats a newer title mention), degradation paths (tasks 500 → run page / commit-status / run-level), partial-task-feed → run-page union surfaced in `degraded`, case/whitespace-insensitive job-union de-dup, `maxPages` truncation surfacing, log unavailability (404, `resource does not exist`, cleaned up, never started), excerpt anchoring, duration parsing/clamping. - `tests/live.test.ts` is **opt-in** (`FORGEJO_LIVE=1`) and self-skips without it — CI has no Forgejo credentials. - `npm test` + `npm run typecheck` green locally, plus `tsc --noUnusedLocals --noUnusedParameters` clean. ## Territory / drift note - Touched: `forgejo-ci/**` (new), `docs/forgejo-ci.md` (new), `README.md` (extension + config rows), `.forgejo/workflows/ci.yaml` (one job). No other extension touched. - The untracked `~/.pi/agent/extensions/forgejo-ci/` is **untouched** (md5s verified unchanged) for the HOMELAB-1740 custodian. Recommended reconciliation: **delete** it once this lands — it is the same 1639 source, now tracked and fixed. Note for 1740: its description guesses that dir is "the working tree of the HOMELAB-1736 fix"; it is not — it predates 1736 by a week and is the 1639 deliverable. - Upstream: this is a Forgejo 14.0.3 bug (`FindAndCount` dropping the LIMIT for `page == 0`). The client-side fix is in place; the upstream-shaped detail (endpoint, request shape, reproduction, source lines) is recorded on HOMELAB-1736 rather than left as a broken tool.
The forgejo_ci_status / forgejo_ci_failures tools the fleet has been calling
live only in the untracked ~/pi/agent/extensions/forgejo-ci/ working dir:
HOMELAB-1639 (2026-09-22) delivered the extension but recorded that nothing
outside ~/.pi/agent/ was modified, so it never landed in this repo. This
commit is a byte-for-byte copy of that working tree (md5-identical) so the
hardening in the next commit is a reviewable diff against known upstream
state, and so the 1740 drift custodian can reconcile by deleting the
untracked copy instead of committing a second, divergent implementation.

Credits HOMELAB-1639 (routes, session-cookie log transport, excerpt design).
HOMELAB-1736: fix(forgejo-ci): page-safe CI status/failure tools with endpoint fallback
All checks were successful
CI / pr-title (pull_request) Successful in 1s
CI / forgejo-ci (pull_request) Successful in 47s
CI / skill-args (pull_request) Successful in 48s
CI / pi-otel-telemetry (pull_request) Successful in 57s
CI / ask-user-question (pull_request) Successful in 32s
CI / todo (pull_request) Successful in 35s
CI / pi-swarm-suite (pull_request) Successful in 3m28s
9db57ba422
Root cause (server-side, client-fixable):
GET /repos/{o}/{r}/actions/tasks honours `limit` ONLY when `page` is also
sent. routers/api/v1/utils/page.go builds ListOptions{Page: FormInt("page")}
(0 when omitted) and models/db/list.go FindAndCount applies the SQL LIMIT only
under `page >= 1`, so ?limit=N asks for the whole table. Measured on this
instance: Sendrast/sendrast-web ?limit=5 returned all 893 rows (443 KB); on
repos with a large action_task table the unbounded query fails with HTTP 500
and a blanked message (services/context/api.go hides 500 bodies from non-admin
callers) — Homelab/infra-core (2274 tasks) 500 after 6.3 s, Homelab/prod (3056)
after 7.1 s. /actions/runs has the same limit-dropping behaviour (?limit=100
returned all 401 runs) and only escaped the 500 because its table is smaller.
The extension called ?limit=100 without page on both endpoints, so every
Homelab repo — the ones the landing pipeline lands — 500'd.

Fix:
- Api.paged() always sends page+limit, walks pages, clamps an over-long page
  defensively, and stops early (tasks are newest-first, so a run lookup stops
  once the run is covered).
- Run lookup uses the server-side run_number filter (exact, one request)
  instead of hoping the run is inside a fixed page window; #N is read as a PR
  number first and a run index second.
- Degradation ladder: actions/tasks -> web run page -> commits/{sha}/status
  contexts -> run-level status. Job-level failure never throws; it reports the
  degraded source in details.degraded and a note: line, so the landing pipeline
  is never blinded again.
- Job durations/indexes now come from the run page (authoritative); task
  updated_at (bumped hours after a job ends — the 43h/2600m displays) is only a
  fallback, clamped to the run duration.
- Log handling: a missing attempt answers 500 ("resource does not exist"), not
  404; cleaned-up/never-started logs are named as such; a failed job whose log
  has no failure marker now gets a bounded tail excerpt instead of "log has no
  failure marker", and excerpts anchor on runner/cancellation markers so a
  cancelled log no longer dumps 30 lines of passing specs.
- Response contract preserved: same text layout and a superset of the previous
  details keys (owner/repo/run/runId/failedJobs; runs[].jobs[].name/status).

Tests: 36 unit/tool tests against a fake Forgejo (paging URLs, clamp,
fallbacks, excerpt extraction, log availability) plus an opt-in live replay
(FORGEJO_LIVE=1) covering infra-core 1481, sendrast-web #489/#490/#492 and
infra-core 1226. Typecheck + vitest gated by a new CI job.

Salvaged from HOMELAB-1639's unlanded working tree (previous commit).
Author
Owner

Independent review (landing worker, HOMELAB-1736) — HOLD, 3 blockers

Reviewed at 9db57ba (CI run #85, all jobs green). Reviewers ran as fresh pi -p --no-session processes, primary sources only (diff + ticket ACs + live Forgejo). Three dimensions: correctness/claim-honesty, design/codebase-fit, test-adequacy/security.

What is verified and good

  • Root cause reproduced independently: /actions/tasks?limit=5 (no page) → 500 on infra-core/prod/lab-director; ?page=1&limit=5 → 200. On sendrast-web, limit is silently ignored (443 680 B = the whole table). The page+limit fix in Api.paged() is correct.
  • Ticket AC replay green: FORGEJO_LIVE=1 → 4/4 (infra-core 1481, sendrast #489/#490/#492, two excerpts). 36 unit tests + typecheck clean.
  • Commit 1 salvage claim verified (5/5 md5-identical to the untracked live dir).
  • details contract genuinely a superset of the old keys; no key removed or retyped.
  • No committed secrets in the diff, the branch history, or package-lock.json.

Blockers — each reproduced against the live server with the post-fix code

  1. Default listing returns stale runs (regression). forgejo_ci_status {repo_ref:"Sendrast/sendrast-web", limit:5} → [484,478,458,408,407] (runsScanned=150 runsTotal=492); the true newest is 492. The pre-fix code on the same input returned [492,491,490,489,488]. Cause: /actions/runs is unordered (?page=1&limit=50 first indices [1,311,7,458,379,8,…]); sampling 150 of 492 and sorting gives the max of a random sample.

  2. PR/branch/sha lookup returns an older match. query:"#147" → run 458; the newest run for PR 147 is 468. Cause: stopWhen: items.some(match) stops at the first page containing any match in an unordered feed. The PR's claim that lookups use the exact run_number filter is true only for run-index refs.

  3. Tasks paging drops a job while reporting a healthy, complete result. {query:"441"} lists only e2e, with jobsSource:"tasks" and degraded:[] — run 441 also has test. Data proof: tasks ?page=2 ends with run 441 e2e (id 12522), ?page=3 starts with run 441 test (id 12521). Cause: stopWhen uses <= (stops exactly at the split run) and resolveJobs' tasks branch never unions the run-page job list. If the dropped job is the failing one, forgejo_ci_failures never quotes it — the AC "keep failure-excerpt capability working end-to-end" is defeated.

Concerns routed to the same fix

  • Run-page rung discards maxAttempt → quotes attempt #1's log for a job on attempt #2 (reproduced with a fake; latent — the real AC runs are all attempt 1).
  • All-sources-down still prints "No failed jobs found for run #N" on a red run (false negative).
  • details.degraded stays [] when jobs came from the run page due to a page-window miss; the "(actions/tasks unavailable)" note text is hardcoded and inaccurate there.
  • forgejo_ci_status fans out one web request per job index per run to compute a maxAttempt it then discards (measured: 22 requests / 8.7 s for 5 runs on infra-core).
  • Test adequacy: the fake Forgejo does not model the real bug (no limit-drop without page, no row-threshold 500), and the regression is guarded mostly by URL-shape assertions. (A revert experiment does go red — 9 failed / 27 passed — so the suite locks the literal URL, but not the server contract.)

Suggestions (follow-up ticket, not this PR): request timeouts, response/log size caps, env-file permission check, repo_ref segment validation, dead code (durationBetween, buildJobPageUrl, ArtifactInfo, JobSource="none"), no-op const full = sha ? sha : sha, and the docs/forgejo-ci.md example that labels run index #489 as "the PR".

Next step: a fresh-context fix worker is producing a follow-up commit on this branch. The PR will be re-reviewed on the delta and merged only if the three blockers are fixed with regression tests that fail on 9db57ba.

## Independent review (landing worker, HOMELAB-1736) — **HOLD, 3 blockers** Reviewed at `9db57ba` (CI run #85, all jobs green). Reviewers ran as fresh `pi -p --no-session` processes, primary sources only (diff + ticket ACs + live Forgejo). Three dimensions: correctness/claim-honesty, design/codebase-fit, test-adequacy/security. **What is verified and good** - Root cause reproduced independently: `/actions/tasks?limit=5` (no `page`) → **500** on infra-core/prod/lab-director; `?page=1&limit=5` → 200. On sendrast-web, `limit` is silently ignored (443 680 B = the whole table). The `page`+`limit` fix in `Api.paged()` is correct. - Ticket AC replay green: `FORGEJO_LIVE=1` → 4/4 (infra-core 1481, sendrast #489/#490/#492, two excerpts). 36 unit tests + typecheck clean. - Commit 1 salvage claim verified (5/5 md5-identical to the untracked live dir). - `details` contract genuinely a superset of the old keys; no key removed or retyped. - No committed secrets in the diff, the branch history, or `package-lock.json`. **Blockers — each reproduced against the live server with the post-fix code** 1. **Default listing returns stale runs (regression).** `forgejo_ci_status {repo_ref:"Sendrast/sendrast-web", limit:5}` → `[484,478,458,408,407]` (`runsScanned=150 runsTotal=492`); the true newest is **492**. The **pre-fix** code on the same input returned `[492,491,490,489,488]`. Cause: `/actions/runs` is unordered (`?page=1&limit=50` first indices `[1,311,7,458,379,8,…]`); sampling 150 of 492 and sorting gives the max of a random sample. 2. **PR/branch/sha lookup returns an older match.** `query:"#147"` → run **458**; the newest run for PR 147 is **468**. Cause: `stopWhen: items.some(match)` stops at the first page containing *any* match in an unordered feed. The PR's claim that lookups use the exact `run_number` filter is true only for run-index refs. 3. **Tasks paging drops a job while reporting a healthy, complete result.** `{query:"441"}` lists only `e2e`, with `jobsSource:"tasks"` and `degraded:[]` — run 441 also has `test`. Data proof: tasks `?page=2` ends with run 441 `e2e` (id 12522), `?page=3` starts with run 441 `test` (id 12521). Cause: `stopWhen` uses `<=` (stops exactly at the split run) and `resolveJobs`' tasks branch never unions the run-page job list. If the dropped job is the failing one, `forgejo_ci_failures` never quotes it — the AC "keep failure-excerpt capability working end-to-end" is defeated. **Concerns routed to the same fix** - Run-page rung discards `maxAttempt` → quotes attempt #1's log for a job on attempt #2 (reproduced with a fake; latent — the real AC runs are all attempt 1). - All-sources-down still prints "No failed jobs found for run #N" on a red run (false negative). - `details.degraded` stays `[]` when jobs came from the run page due to a page-window miss; the "(actions/tasks unavailable)" note text is hardcoded and inaccurate there. - `forgejo_ci_status` fans out one web request per job index per run to compute a `maxAttempt` it then discards (measured: 22 requests / 8.7 s for 5 runs on infra-core). - Test adequacy: the fake Forgejo does not model the real bug (no limit-drop without `page`, no row-threshold 500), and the regression is guarded mostly by URL-shape assertions. (A revert experiment *does* go red — 9 failed / 27 passed — so the suite locks the literal URL, but not the server contract.) **Suggestions (follow-up ticket, not this PR)**: request timeouts, response/log size caps, env-file permission check, `repo_ref` segment validation, dead code (`durationBetween`, `buildJobPageUrl`, `ArtifactInfo`, `JobSource="none"`), no-op `const full = sha ? sha : sha`, and the `docs/forgejo-ci.md` example that labels run index `#489` as "the PR". **Next step**: a fresh-context fix worker is producing a follow-up commit on this branch. The PR will be re-reviewed on the delta and merged only if the three blockers are fixed with regression tests that fail on `9db57ba`.
HOMELAB-1736: fix(forgejo-ci): correct run ordering, newest-match lookup, and split-run paging (review blockers)
All checks were successful
CI / pr-title (pull_request) Successful in 1s
CI / ask-user-question (pull_request) Successful in 45s
CI / forgejo-ci (pull_request) Successful in 48s
CI / skill-args (pull_request) Successful in 52s
CI / pi-otel-telemetry (pull_request) Successful in 58s
CI / todo (pull_request) Successful in 57s
CI / pi-swarm-suite (pull_request) Successful in 3m28s
911e0d71b8
Follow-up to the PR #48 review. Three independently reproduced blockers plus
four concerns, each with a regression test that fails on 9db57ba and passes here.

F1 no-query listing sampled only 3 pages (150 of 492) of the UNORDERED
   /actions/runs feed and presented the max of that sample as "the newest runs"
   (live pre-fix: 484,478,458,408,407). lookupRuns now covers the whole feed
   (bounded by total_count) and sorts by index_in_repo.
   live before: sendrast-web 484,478,458,408,407; infra-core max 1399
   live after:  sendrast-web [492,491,490,489,488];
                infra-core   [1509,1508,1507,1506,1505]

F2 PR/branch/sha lookups stopped at the first page containing any match in the
   unordered feed, returning an older match. They now cover the whole feed and
   take the newest.
   live before: #147 -> 458; main -> 311; #157 -> 491,480
   live after:  #147 -> 468; main -> 492; #157 -> 492

F3 tasks paging stopped with `<=` exactly at a run split across a page
   boundary (dropping a job), and the tasks branch never unioned run-page jobs.
   stopWhen is now `<` (one page past the oldest selected run) and meta-only
   jobs are unioned in.
   live before: run 441 -> [e2e]
   live after:  run 441 -> [e2e, test]

F4 when every job-level source failed on a red run, forgejo_ci_failures still
   printed "No failed jobs found". It now emits
   "job-level failure detail unavailable; run status=<x>".

F5 a task-page-window miss left details.degraded empty while the note hardcoded
   "(actions/tasks unavailable)". The miss is now recorded in degraded
   ("task page window did not cover run N") with an accurate note.

F6 the run-page rung dropped maxAttempt, so failures quoted attempt 1 for a job
   actually on attempt 2. maxAttempt is propagated (and fetchJobsMeta walks every
   job page when attempts are needed) so the latest attempt's log is quoted.

F7 removed dead code (durationBetween, buildJobPageUrl, ArtifactInfo,
   JobSource "none"), removed the no-op `full`, validated repo_ref segments
   (reject "." / ".." and URL metacharacters) and encoded path segments, fixed
   the stale JobView.source doc and the false "One request." comment, and
   stopped the status path from fanning out one web request per job.

Tests: tests/regression.test.ts (11 tests) all fail on 9db57ba and pass here;
tests/fake.ts gains a stateful modeledForgejo (page-keyed responses, no-page
table/500, unordered runs feed, split tasks feed). tests/live.test.ts extended
with the F1/F2/F3 live assertions above. npm test -> 47 passed / 9 skipped;
tsc --noEmit --noUnusedLocals --noUnusedParameters clean.

Deliberately not fixed (r3 concerns, non-blocking): request timeouts,
response/log size caps, env-file permission warnings, 429 backoff, log
redaction.
Author
Owner

Follow-up commit — review blockers fixed

Landed 911e0d7 on top of 9db57ba (fast-forward push, no force-push). All three BLOCKERs (F1–F3) plus concerns F4–F7 are fixed, each with a regression test that fails on 9db57ba.

F1 — no-query listing returned stale runs (regression)

Root cause: /actions/runs is unordered and lookupRuns sampled only 3 pages (150/492) then sorted the sample.

  • before: Sendrast/sendrast-web limit:5 -> 484,478,458,408,407; Homelab/infra-core max 1399
  • after: Sendrast/sendrast-web -> [492,491,490,489,488]; Homelab/infra-core -> [1509,1508,1507,1506,1505]
    Fix: cover the whole feed (bounded by total_count) and take max(index_in_repo).

F2 — PR/branch/sha lookup returned an older match

Root cause: early stopWhen stopped at the first page containing any match in an unordered feed.

  • before: #147 -> 458; main -> 311; #157 -> 491,480
  • after: #147 -> 468; main -> 492; #157 -> 492
    Fix: full-feed scan, return the newest match. A run index still uses the exact run_number filter (one request).

F3 — tasks paging dropped a job while reporting a healthy complete result

Root cause: stopWhen <= stopped exactly at the run split across the page boundary, and the tasks branch never unioned run-page jobs.

  • before: Sendrast/sendrast-web query:441 -> ["e2e"], jobsSource:"tasks", degraded:[]
  • after: -> ["e2e","test"]
    Fix: strict < (fetch one page past the oldest selected run) + union meta-only jobs.

Concerns

  • F4 a red run with every job source down now emits job-level failure detail unavailable; run status=<x> instead of No failed jobs found for run #N.
  • F5 a task-page-window miss is now recorded in details.degraded (task page window did not cover run N) with an accurate note, instead of degraded:[] plus the hardcoded "(actions/tasks unavailable)".
  • F6 the run-page rung propagates maxAttempt, so forgejo_ci_failures quotes the latest attempt's log (regression test uses data-attempt-number="2").
  • F7 removed dead code (durationBetween, buildJobPageUrl, ArtifactInfo, JobSource "none"), removed the no-op full, validated repo_ref segments (reject ./.. and URL metacharacters), encoded path segments, fixed stale docs/comments, and stopped the status path fanning out one web request per job.

Tests

  • tests/regression.test.ts (11 tests): all fail on 9db57ba, pass after.
  • tests/fake.ts gains a stateful modeledForgejo (page-keyed responses, no-page table/500, unordered runs feed, split tasks feed).
  • tests/live.test.ts extended with the F1/F2/F3 live assertions.
  • npm test -> 47 passed / 9 skipped; tsc --noEmit --noUnusedLocals --noUnusedParameters clean; FORGEJO_LIVE=1 vitest run tests/live.test.ts -> 9/9.

Deliberately not fixed (r3 concerns, non-blocking)

Request timeouts, response/log size caps, env-file permission warnings, 429 backoff, log redaction. Left for a separate change.

## Follow-up commit — review blockers fixed Landed `911e0d7` on top of `9db57ba` (fast-forward push, no force-push). All three BLOCKERs (F1–F3) plus concerns F4–F7 are fixed, each with a regression test that fails on `9db57ba`. ### F1 — no-query listing returned stale runs (regression) Root cause: `/actions/runs` is unordered and `lookupRuns` sampled only 3 pages (150/492) then sorted the sample. - before: `Sendrast/sendrast-web limit:5` -> `484,478,458,408,407`; `Homelab/infra-core` max `1399` - after: `Sendrast/sendrast-web` -> `[492,491,490,489,488]`; `Homelab/infra-core` -> `[1509,1508,1507,1506,1505]` Fix: cover the whole feed (bounded by `total_count`) and take `max(index_in_repo)`. ### F2 — PR/branch/sha lookup returned an older match Root cause: early `stopWhen` stopped at the first page containing any match in an unordered feed. - before: `#147` -> `458`; `main` -> `311`; `#157` -> `491,480` - after: `#147` -> `468`; `main` -> `492`; `#157` -> `492` Fix: full-feed scan, return the newest match. A run **index** still uses the exact `run_number` filter (one request). ### F3 — tasks paging dropped a job while reporting a healthy complete result Root cause: `stopWhen <=` stopped exactly at the run split across the page boundary, and the tasks branch never unioned run-page jobs. - before: `Sendrast/sendrast-web query:441` -> `["e2e"]`, `jobsSource:"tasks"`, `degraded:[]` - after: `-> ["e2e","test"]` Fix: strict `<` (fetch one page past the oldest selected run) + union meta-only jobs. ### Concerns - **F4** a red run with every job source down now emits `job-level failure detail unavailable; run status=<x>` instead of `No failed jobs found for run #N.` - **F5** a task-page-window miss is now recorded in `details.degraded` (`task page window did not cover run N`) with an accurate note, instead of `degraded:[]` plus the hardcoded "(actions/tasks unavailable)". - **F6** the run-page rung propagates `maxAttempt`, so `forgejo_ci_failures` quotes the latest attempt's log (regression test uses `data-attempt-number="2"`). - **F7** removed dead code (`durationBetween`, `buildJobPageUrl`, `ArtifactInfo`, `JobSource "none"`), removed the no-op `full`, validated `repo_ref` segments (reject `.`/`..` and URL metacharacters), encoded path segments, fixed stale docs/comments, and stopped the status path fanning out one web request per job. ### Tests - `tests/regression.test.ts` (11 tests): all fail on `9db57ba`, pass after. - `tests/fake.ts` gains a stateful `modeledForgejo` (page-keyed responses, no-page table/500, unordered runs feed, split tasks feed). - `tests/live.test.ts` extended with the F1/F2/F3 live assertions. - `npm test` -> 47 passed / 9 skipped; `tsc --noEmit --noUnusedLocals --noUnusedParameters` clean; `FORGEJO_LIVE=1 vitest run tests/live.test.ts` -> 9/9. ### Deliberately not fixed (r3 concerns, non-blocking) Request timeouts, response/log size caps, env-file permission warnings, 429 backoff, log redaction. Left for a separate change.
HOMELAB-1736: fix(forgejo-ci): rank branch/PR matches by precision so a title match cannot win (delta blocker)
All checks were successful
CI / pr-title (pull_request) Successful in 1s
CI / ask-user-question (pull_request) Successful in 32s
CI / pi-otel-telemetry (pull_request) Successful in 44s
CI / skill-args (pull_request) Successful in 50s
CI / forgejo-ci (pull_request) Successful in 50s
CI / todo (pull_request) Successful in 1m7s
CI / pi-swarm-suite (pull_request) Successful in 3m40s
c9ade35ab7
Delta review blocker: the F2 whole-feed "newest match" let a newer run on a
different branch win when its title merely contained the queried branch.
Reproduced live: Homelab/prod query "live" returned run 1163 (prettyref
"#604", title "Merge branch 'live' into ...") while the newest run whose
prettyref === "live" is 1046.

FIX A (blocker) — matchTier() scores a match 2 (exact prettyref / exact PR
number) or 1 (loose title substring); lookupRuns()/resolveRunFromList() keep
only the highest tier, then the newest within it. index/sha unchanged.
Live before -> after (newest returned run):
  Homelab/prod          "live"  ->  1163  ->  1046
  Homelab/infra-core    "live"  ->  1509  ->  1509
  Sendrast/sendrast-web "main"  ->   492  ->   492
  Sendrast/sendrast-web "#147"  ->   468  ->   468

FIX B (concern) — when actions/tasks returned rows for a run but the run page
supplied a missing job via the F3 union, degraded stayed [] and jobsSource
stayed "tasks". resolveJobs() now reports filledFromRunPage and both tools
push "task rows for run N were incomplete; filled from the run page" into
degraded (+ a note). Before: {"degraded":[],"jobsSource":"tasks", jobs:
[e2e(tasks), test(run-page)]}; after: degraded non-empty.

FIX C (quality):
  - forgejo.test.ts early-stop test renamed/rewritten: the match is on page 1
    and the whole 3-page feed is still scanned.
  - regression F6 test now returns a distinct body per attempt and asserts the
    excerpt contains the attempt-2 marker.
  - job-union de-dup keys are normalised (trim+lowercase) so E2E/e2e cannot
    double-count.
  - runsTruncated is surfaced in details and a "run history truncated ..."
    note whenever the full-feed walk hits the maxPages cap.

Tests: npm test 52 passed / 11 skipped; typecheck clean; live suite 11 passed
(incl. Homelab/prod "live" -> 1046). 5 new regression tests are RED on 911e0d7.
Author
Owner

FIX WORKER #2 — delta blocker fixed (c9ade35)

FIX A (blocker) — branch/PR matching returned a run on a different branch. matchesRunQuery OR'd prettyref === value with title.includes(value), so the whole-feed newest-match took a run on another branch whose title merely mentioned the queried branch.

Ranking is now precision first, recency second: matchTier() marks an exact prettyref / exact prNumberOfRun match as tier 2 and a loose title substring as tier 1; lookupRuns() keeps only the best tier, then the newest within it. index/sha unchanged.

Live before → after (newest returned run, real Forgejo):

repo query before after
Homelab/prod live 1163 (prettyref "#604", title mentions live) 1046 (prettyref === "live")
Homelab/infra-core live 1509 1509
Sendrast/sendrast-web main 492 492
Sendrast/sendrast-web #147 468 468

FIX B (concern) — a partial task feed was hidden from degraded. When actions/tasks returned only some of a run's rows, the F3 union completed the list from the run page while jobsSource stayed "tasks" and degraded stayed []. resolveJobs() now reports filledFromRunPage; both tools push task rows for run N were incomplete; filled from the run page into degraded (plus a note). Before: {"degraded":[],"jobsSource":"tasks","jobs":[e2e(tasks),test(run-page)]}; after: degraded non-empty.

FIX C (quality). The whole-feed scan test is renamed/rewritten (the early stop is gone); the F6 attempt test returns a distinct body per attempt and asserts the attempt-2 marker; union de-dup keys are normalised (trim+lowercase); and runsTruncated is surfaced in details plus a text note when the maxPages cap is hit.

Verification

  • npm test → 52 passed / 11 skipped; tsc --noEmit --noUnusedLocals --noUnusedParameters clean.
  • FORGEJO_LIVE=1 npx vitest run tests/live.test.ts → 11/11 passed, including the new Homelab/prod live → 1046 and Homelab/infra-core live → 1509 assertions.
  • 5 new regression tests are RED on 911e0d7 (FIX A branch + PR precision, FIX B degraded, FIX C normalisation + truncation); source delta is 911e0d7..c9ade35.
## FIX WORKER #2 — delta blocker fixed (`c9ade35`) **FIX A (blocker) — branch/PR matching returned a run on a different branch.** `matchesRunQuery` OR'd `prettyref === value` with `title.includes(value)`, so the whole-feed newest-match took a run on another branch whose title merely mentioned the queried branch. Ranking is now **precision first, recency second**: `matchTier()` marks an exact `prettyref` / exact `prNumberOfRun` match as tier 2 and a loose title substring as tier 1; `lookupRuns()` keeps only the best tier, then the newest within it. `index`/`sha` unchanged. Live before → after (newest returned run, real Forgejo): | repo | query | before | after | |------|-------|--------|-------| | Homelab/prod | `live` | 1163 (`prettyref "#604"`, title mentions `live`) | **1046** (`prettyref === "live"`) | | Homelab/infra-core | `live` | 1509 | 1509 | | Sendrast/sendrast-web | `main` | 492 | 492 | | Sendrast/sendrast-web | `#147` | 468 | 468 | **FIX B (concern) — a partial task feed was hidden from `degraded`.** When `actions/tasks` returned only some of a run's rows, the F3 union completed the list from the run page while `jobsSource` stayed `"tasks"` and `degraded` stayed `[]`. `resolveJobs()` now reports `filledFromRunPage`; both tools push `task rows for run N were incomplete; filled from the run page` into `degraded` (plus a note). Before: `{"degraded":[],"jobsSource":"tasks","jobs":[e2e(tasks),test(run-page)]}`; after: `degraded` non-empty. **FIX C (quality).** The whole-feed scan test is renamed/rewritten (the early stop is gone); the F6 attempt test returns a distinct body per attempt and asserts the attempt-2 marker; union de-dup keys are normalised (trim+lowercase); and `runsTruncated` is surfaced in `details` plus a text note when the `maxPages` cap is hit. **Verification** - `npm test` → 52 passed / 11 skipped; `tsc --noEmit --noUnusedLocals --noUnusedParameters` clean. - `FORGEJO_LIVE=1 npx vitest run tests/live.test.ts` → 11/11 passed, including the new `Homelab/prod live → 1046` and `Homelab/infra-core live → 1509` assertions. - 5 new regression tests are **RED on `911e0d7`** (FIX A branch + PR precision, FIX B `degraded`, FIX C normalisation + truncation); source delta is `911e0d7..c9ade35`.
Author
Owner

Landing review complete at c9ade35 — merging

Three-stage independent review (fresh pi -p --no-session processes, primary sources only), two fix cycles, all verified against the live Forgejo.

Stage 1 (9db57ba) — 3 blockers, all reproduced with the post-fix code and one a regression against the pre-fix tool:

  1. no-query listing sampled 150 of 492 unordered runs and reported an old run as newest ([484,478,458,408,407] vs true 492; the pre-fix tool returned [492,491,490,489,488]);
  2. PR/branch/sha lookups stopped at the first page containing any match (#147 -> 458 vs true newest 468);
  3. tasks paging stopped with <= exactly at a run split across a page boundary (run 441 -> only e2e, reported with degraded: []).

Stage 2 (911e0d7) — F1/F2/F3 fixed and those 11 regression tests verified RED on 9db57ba / GREEN here. But the delta review found a NEW blocker: with the early stop removed, a newer run on a different branch whose title merely contained the queried branch name won (Homelab/prod live -> run 1163 on #604 instead of the true newest live run 1046).

Stage 3 (c9ade35) — VERDICT: CONCERN (no blockers). Branch/PR matching is now ranked by precision before recency; the 5 new tests are honest (RED on 911e0d7, GREEN here); the healthy path is not turned into a false alarm.

Independent live verification of this head (landing worker, real registered tool, real server):

check result
Homelab/prod live [1046, 987, 984, 983, 982] — correct (was 1163)
Homelab/infra-core live [1509, 1508, 1499, 1491, 1490]
Sendrast/sendrast-web main [492, 491, 490, 489, 483]
Sendrast/sendrast-web #147 [468, 464, 458] — correct (was 458 only)
Sendrast/sendrast-web no-query [492, 491, 490, 489, 488], scanned 492/492
Homelab/infra-core no-query [1509, 1508, 1507, 1506, 1505], scanned 401/401
run 441 jobs ["e2e", "test"] — both present (was ["e2e"])
AC #489 e2e:cancelled, test:success; excerpt still quotes the cancelled-e2e log

npm test 52 passed / 11 skipped, tsc --noEmit --noUnusedLocals --noUnusedParameters clean, FORGEJO_LIVE=1 11/11. CI run #87 green on all 7 jobs.

Open concerns carried to HOMELAB-1749 (not blocking)

  • The tier ranking is applied within one candidate interpretation, not across candidateQueries, so a loose #N title mention can still shadow an available exact run-index match. Pre-existing in all three commits (identical result on 9db57ba/911e0d7), no live repo currently triggers it, and #N-as-PR is the documented primary reading — so it merges, but the README's precision invariant should be read as per-interpretation until this is fixed.
  • The retained loose title.includes() fallback is very broad when a repo has zero exact matches: Sendrast/sendrast-web live returns [488,417,416,406,403] (488 matched only because "outlive".includes("live")). Worth a token-boundary match or an explicit "no exact branch match" outcome.
  • forgejo_ci_failures does not surface runsScanned/runsTotal/runsTruncated (only forgejo_ci_status does), so at the maxPages cap it could silently operate on a truncated "newest". Unreachable today (prod 833 / infra-core 401 / sendrast 492 runs).
  • jobKey() lowercasing can merge two genuinely case-distinct jobs.
  • resolveRunFromList is production-dead (test-only helper).
  • modeledForgejo's no-page/blank-500 branch is still not driven by any test.

Plus the earlier suggestion-tier items (request timeouts, response/log size caps, env-file permission check, 429 backoff, log redaction) — all tracked in HOMELAB-1749.

## Landing review complete at `c9ade35` — **merging** Three-stage independent review (fresh `pi -p --no-session` processes, primary sources only), two fix cycles, all verified against the live Forgejo. **Stage 1** (`9db57ba`) — 3 blockers, all reproduced with the post-fix code and one a *regression* against the pre-fix tool: 1. no-query listing sampled 150 of 492 unordered runs and reported an old run as newest (`[484,478,458,408,407]` vs true `492`; the pre-fix tool returned `[492,491,490,489,488]`); 2. PR/branch/sha lookups stopped at the first page containing any match (`#147` -> 458 vs true newest 468); 3. tasks paging stopped with `<=` exactly at a run split across a page boundary (run 441 -> only `e2e`, reported with `degraded: []`). **Stage 2** (`911e0d7`) — F1/F2/F3 fixed and those 11 regression tests verified RED on `9db57ba` / GREEN here. But the delta review found a NEW blocker: with the early stop removed, a newer run on a *different* branch whose **title merely contained** the queried branch name won (`Homelab/prod` `live` -> run 1163 on `#604` instead of the true newest `live` run 1046). **Stage 3** (`c9ade35`) — `VERDICT: CONCERN` (no blockers). Branch/PR matching is now ranked by precision before recency; the 5 new tests are honest (RED on `911e0d7`, GREEN here); the healthy path is not turned into a false alarm. **Independent live verification of this head** (landing worker, real registered tool, real server): | check | result | |---|---| | `Homelab/prod` `live` | `[1046, 987, 984, 983, 982]` — correct (was 1163) | | `Homelab/infra-core` `live` | `[1509, 1508, 1499, 1491, 1490]` | | `Sendrast/sendrast-web` `main` | `[492, 491, 490, 489, 483]` | | `Sendrast/sendrast-web` `#147` | `[468, 464, 458]` — correct (was 458 only) | | `Sendrast/sendrast-web` no-query | `[492, 491, 490, 489, 488]`, scanned 492/492 | | `Homelab/infra-core` no-query | `[1509, 1508, 1507, 1506, 1505]`, scanned 401/401 | | run 441 | jobs `["e2e", "test"]` — both present (was `["e2e"]`) | | AC `#489` | `e2e:cancelled`, `test:success`; excerpt still quotes the cancelled-e2e log | `npm test` 52 passed / 11 skipped, `tsc --noEmit --noUnusedLocals --noUnusedParameters` clean, `FORGEJO_LIVE=1` 11/11. CI run #87 green on all 7 jobs. **Open concerns carried to HOMELAB-1749 (not blocking)** - The tier ranking is applied *within* one candidate interpretation, not *across* `candidateQueries`, so a loose `#N` title mention can still shadow an available exact run-index match. Pre-existing in all three commits (identical result on `9db57ba`/`911e0d7`), no live repo currently triggers it, and `#N`-as-PR is the documented primary reading — so it merges, but the README's precision invariant should be read as per-interpretation until this is fixed. - The retained loose `title.includes()` fallback is very broad when a repo has zero exact matches: `Sendrast/sendrast-web` `live` returns `[488,417,416,406,403]` (488 matched only because `"outlive".includes("live")`). Worth a token-boundary match or an explicit "no exact branch match" outcome. - `forgejo_ci_failures` does not surface `runsScanned/runsTotal/runsTruncated` (only `forgejo_ci_status` does), so at the `maxPages` cap it could silently operate on a truncated "newest". Unreachable today (prod 833 / infra-core 401 / sendrast 492 runs). - `jobKey()` lowercasing can merge two genuinely case-distinct jobs. - `resolveRunFromList` is production-dead (test-only helper). - `modeledForgejo`'s no-`page`/blank-500 branch is still not driven by any test. Plus the earlier suggestion-tier items (request timeouts, response/log size caps, env-file permission check, 429 backoff, log redaction) — all tracked in HOMELAB-1749.
aaron merged commit 3d3171015b into main 2026-09-30 03:39:52 +00:00
Author
Owner

LANDED — PR #48 merged

Merge SHA: 3d3171015b4f4428dd00a1fbd983f9cae08ccddb (merge commit on main, first parent 17ecc90)
Branch: plane/HOMELAB-1736-forgejo-ci kept (not deleted, matching repo convention)
Landed commits: b1aff66 (salvage), 9db57ba (fix 1), 911e0d7 (review blockers), c9ade35 (delta blocker)
CI: run #87 green on all 7 jobs pre-merge; run #88 on main post-merge
Review: 3 independent fresh-context passes + 2 fix cycles; final verdict CONCERN (no open blockers)

Live checkout reconciliation

~/.pi/agent/extensions had forgejo-ci/ as an untracked dir, so a plain ff-merge would have aborted ("untracked files would be overwritten"). Executed:

  1. md5 gate — the untracked dir's 5 files vs b1aff66:forgejo-ci/: 5/5 byte-identical
    (ea98ffac… README.md, 341a4105… forgejo-ci.env.example, cf20ff56… forgejo.ts, ce96e86e… index.ts, 70b7b80b… types.ts). Re-verified immediately before the delete. A tarball backup was taken first as belt-and-braces (the content is also in git at b1aff66).
  2. Removed only forgejo-ci/ (behind that gate).
  3. git merge --ff-only forgejo/main → 17ecc90..3d31710, clean fast-forward.
  4. forgejo-ci/ now exists as 13 tracked files at HEAD; all 12 non-lockfile files byte-match c9ade35, the reviewed commit.
  5. Drift preserved: the 17 pre-existing modified files (pi-swarm-suite/**, pi-skills/**) and the 2 untracked dirs (pi-google-docs/skills/, probe-hasui/) are untouched — the only git status delta versus before is the removal of ?? forgejo-ci/. HOMELAB-1740 owns the rest.

Post-merge tool verification

  • My running session still returns Forgejo API 500 on …/actions/tasks?limit=100 — expected: extensions are loaded at session start, so the running process holds the pre-fix code in memory. No session was restarted. Running sessions keep old code until they restart; new sessions get the fix.
  • Verified through a fresh process (new session, loads the newly-tracked extension):
    • forgejo_ci_status Homelab/infra-core → no 500; runs #1509/#1508/#1507 with build-and-push success 1m57s/28s/1m39s (and 1509 is the correct newest — the pre-fix tool's max was 1399).
    • forgejo_ci_failures Sendrast/sendrast-web 489 → red run with the cancelled e2e job (index 1, 9m49s, #1:cancelled) and a real log excerpt anchored on context canceled / 🏁 Job failed.
  • ~/repos/pi-extensions (the other checkout) is unchanged — byte-identical git status to its pre-merge state. Note it is stale/diverged (8fe0370, a local-only commit absent from Forgejo, 2 PRs behind) — flagged, not touched.

GitOps continuation

pi-extensions has no image build or manifest pipeline — it is consumed as a source checkout (.forgejo/workflows/ci.yaml runs CI only). The continuation is therefore the checkout reconciliation above, which is complete and verified. Post-merge CI on main is run #88.

Residual (non-blocking) findings → HOMELAB-1749

rankMatches tiering is applied per candidate interpretation, not across candidateQueries (a loose #N title mention can still shadow an exact run-index match; pre-existing in all commits, no live repo triggers it); the loose title.includes() branch fallback is broad when a repo has zero exact matches; forgejo_ci_failures omits runsScanned/runsTotal/runsTruncated; jobKey() lowercasing can merge case-distinct jobs; resolveRunFromList is production-dead. Plus the suggestion tier: request timeouts, response/log size caps, env-file permission check, 429 backoff, log redaction, and driving modeledForgejo's no-page/500 branch from a test.

## LANDED — PR #48 merged **Merge SHA:** `3d3171015b4f4428dd00a1fbd983f9cae08ccddb` (merge commit on `main`, first parent `17ecc90`) **Branch:** `plane/HOMELAB-1736-forgejo-ci` kept (not deleted, matching repo convention) **Landed commits:** `b1aff66` (salvage), `9db57ba` (fix 1), `911e0d7` (review blockers), `c9ade35` (delta blocker) **CI:** run #87 green on all 7 jobs pre-merge; run #88 on `main` post-merge **Review:** 3 independent fresh-context passes + 2 fix cycles; final verdict `CONCERN` (no open blockers) ### Live checkout reconciliation `~/.pi/agent/extensions` had `forgejo-ci/` as an **untracked** dir, so a plain ff-merge would have aborted ("untracked files would be overwritten"). Executed: 1. **md5 gate** — the untracked dir's 5 files vs `b1aff66:forgejo-ci/`: **5/5 byte-identical** (`ea98ffac… README.md`, `341a4105… forgejo-ci.env.example`, `cf20ff56… forgejo.ts`, `ce96e86e… index.ts`, `70b7b80b… types.ts`). Re-verified immediately before the delete. A tarball backup was taken first as belt-and-braces (the content is also in git at `b1aff66`). 2. Removed **only** `forgejo-ci/` (behind that gate). 3. `git merge --ff-only forgejo/main` → `17ecc90..3d31710`, clean fast-forward. 4. `forgejo-ci/` now exists as **13 tracked files** at HEAD; all 12 non-lockfile files byte-match `c9ade35`, the reviewed commit. 5. **Drift preserved**: the 17 pre-existing modified files (`pi-swarm-suite/**`, `pi-skills/**`) and the 2 untracked dirs (`pi-google-docs/skills/`, `probe-hasui/`) are untouched — the only `git status` delta versus before is the removal of `?? forgejo-ci/`. HOMELAB-1740 owns the rest. ### Post-merge tool verification - My **running** session still returns `Forgejo API 500 on …/actions/tasks?limit=100` — **expected**: extensions are loaded at session start, so the running process holds the pre-fix code in memory. **No session was restarted.** Running sessions keep old code until they restart; **new sessions get the fix.** - Verified through a **fresh process** (new session, loads the newly-tracked extension): - `forgejo_ci_status Homelab/infra-core` → **no 500**; runs #1509/#1508/#1507 with `build-and-push success 1m57s/28s/1m39s` (and 1509 is the correct newest — the pre-fix tool's max was 1399). - `forgejo_ci_failures Sendrast/sendrast-web 489` → red run with the cancelled `e2e` job (`index 1, 9m49s, #1:cancelled`) and a real log excerpt anchored on `context canceled` / `🏁 Job failed`. - `~/repos/pi-extensions` (the other checkout) is **unchanged** — byte-identical `git status` to its pre-merge state. Note it is stale/diverged (`8fe0370`, a local-only commit absent from Forgejo, 2 PRs behind) — flagged, not touched. ### GitOps continuation `pi-extensions` has **no image build or manifest pipeline** — it is consumed as a source checkout (`.forgejo/workflows/ci.yaml` runs CI only). The continuation is therefore the checkout reconciliation above, which is complete and verified. Post-merge CI on `main` is run #88. ### Residual (non-blocking) findings → HOMELAB-1749 `rankMatches` tiering is applied per candidate interpretation, not across `candidateQueries` (a loose `#N` title mention can still shadow an exact run-index match; pre-existing in all commits, no live repo triggers it); the loose `title.includes()` branch fallback is broad when a repo has zero exact matches; `forgejo_ci_failures` omits `runsScanned/runsTotal/runsTruncated`; `jobKey()` lowercasing can merge case-distinct jobs; `resolveRunFromList` is production-dead. Plus the suggestion tier: request timeouts, response/log size caps, env-file permission check, 429 backoff, log redaction, and driving `modeledForgejo`'s no-`page`/500 branch from a test.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/pi-extensions!48
No description provided.