HOMELAB-2682: fix(forgejo-ci): a commit-status pending job is pending, not a pass #116
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "plane/ci-pending-status-truth"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
I reproduced a false claim in
forgejo_ci_status, and this fixes it.What I reproduced
Reading
origin/mainataa6d009(not a stale local main), the path is: whenactions/tasksis unhealthy and the web run page is unavailable,resolveJobsfalls back tocommits/{sha}/statusand builds the job list fromcommit-status contexts. Commit statuses use
pendingfor a check that has notconcluded.
isPending()inforgejo-ci/forgejo.tslistedrunning | waiting | queued | in_progress | blockedand notpending, sorenderStatus()saw no failing job and no pending job and chosepass.Reproduction: one run whose status is
running,actions/tasksreturning theblank 500, and two commit statuses —
build-and-push: successande2e: pending. The tool printed:[pass]on a run with an unfinished job is a false statement about CI, and theper-job line for that job used the unknown-status icon. Both come from the same
gap:
pendingwas not in the status vocabulary.What changed
forgejo-ci/forgejo.ts—isPending()now includespending(sorenderStatusreportsrunningfor such a run). Three comment lines say why.forgejo-ci/index.ts—STATUS_ICON.pending = "⏳", so the per-job line is notan unknown-status bullet.
forgejo-ci/tests/tools.test.ts— new tool-level case: the scenario above mustprint
run #1481 c7a74c7d4a #1665 [running], must not contain[pass], andmust show
⏳ e2e pending.forgejo-ci/tests/forgejo.test.ts— new unit case pinning the vocabulary:isPending("pending"),isPending("PENDING")true,isPending("success")false,
isFailed("pending")false.Nothing else in the package consumes
isPending, so the change is confined tohow a status is rendered.
Red then green
Tests were written first, against unmodified source:
Test Files 2 failed | 1 passed | 1 skipped (4),Tests 2 failed | 52 passed | 11 skipped (65)— the tool-level case failed with the[pass]outputquoted above, and
expect(isPending("pending")).toBe(true)receivedfalse.Test Files 3 passed | 1 skipped (4),Tests 54 passed | 11 skipped (65).forgejo-ci/tests/live.test.tsis skipped by design(it needs a live Forgejo session).
npx tsc --noEmitinforgejo-ci: exit 0, no output.What I did not measure
I did not observe a live Forgejo run in the
pendingcommit-status state; thecase is constructed in the fake server from the documented commit-status
vocabulary. The commit-status fallback path itself is unchanged.
pendingjob as pending, not a passA job whose status arrives from `commits/{sha}/status` (the fallback when actions/tasks is unhealthy) can be `pending`. isPending() did not list it, so renderStatus() fell through to [pass] for a run with an unfinished job, and the per-job line printed an unknown-status icon. - isPending(): include `pending` - STATUS_ICON: pending -> ⏳ - tests: red-then-green unit case for the vocabulary + tool-level case pinning that a pending commit status renders [running], never [pass]fix(forgejo-ci): a commit-status pending job is pending, not a passto HOMELAB-2682: fix(forgejo-ci): a commit-status pending job is pending, not a passVerdict of record — ld-reviewer — head
f5de0ed1d2: APPROVEmodel: omniroute/deepseek-flash
session: 01a1b3e6-fd99-70d7-89c1-cc264f3715c1
Blockers
None.
Measurements
Diff scope.
git diff aa6d009..f5de0ed= 2 commits (72c18abfix,f5de0edCI retrigger) and 4 files, all inside the pending leg:forgejo-ci/forgejo.ts(isPendinggainspending+ 3 comment lines),forgejo-ci/index.ts(STATUS_ICON.pending = "⏳"),tests/forgejo.test.ts(vocabulary unit case),tests/tools.test.ts(tool-level case). Nothing outside the pending leg; no unrelated edit, no dependency or config change.Before-state, reproduced by me. Local clone at base
aa6d009, fake server: one runstatus: running,actions/tasksreturning the blank 500,commits/{sha}/status=build-and-push: success+e2e: pending, no session cookie (so the web run page is unavailable). Output:Byte-for-byte the output quoted in the PR body. The false
[pass]on a run with an unfinished job is real at main.After-state, same scenario, head
f5de0ed. Same harness, same route table:Both halves of the complaint are fixed: the header no longer says pass, and the per-job line uses the pending icon instead of the unknown-status bullet.
Pending-leg completeness. The live Forgejo swagger (
forgejo.aaron.reynoza.org/swagger.v1.json, read today) definesCommitStatusStateas "pending", "success", "error", "failure", "warning" — sopendingis the only unfinished commit-status spelling, and it is now listed. Actions task states that are unfinished (waiting,running,blocked) were already listed, as were the extraqueued/in_progress. My spelling matrix at head, one job in each state, commit-status fallback path:No unfinished spelling renders
[pass]at head.Tests, run by me at head (
/tmpclone checked out atf5de0ed):Test Files 3 passed | 1 skipped (4),Tests 54 passed | 11 skipped (65);tests/live.test.tsskipped by design.npx tsc --noEmitexit 0, no output. Matches the PR body's numbers.Red check, run by me. The PR's two new test files placed against unmodified base source, same suite:
Test Files 2 failed | 1 passed | 1 skipped (4),Tests 2 failed | 52 passed | 11 skipped (65); the tool-level case failed on[pass]and the unit case onexpect(isPending("pending")).toBe(true)receivingfalse. Exactly the claimed red.Measured, not a blocker. A concluded
warningcommit status renders[pass]with the unknown-status bullet.warningis a concluded state in the same swagger enum, not an unfinished check, and it is outside this PR's pending leg — recorded here only so the next reader knows it was seen.Exposure
I read the PR #116 metadata and full diff,
forgejo-ci/forgejo.ts(isPending,isFailed,jobsFromCommitStatuses),forgejo-ci/index.ts(renderStatus,STATUS_ICON, the resolveJobs commit-status fallback), both new test cases, and the public Forgejo swagger enum. I ran, in two throwaway clones at/tmp/rv116(baseaa6d00972ad9e4d2df4e9c68f24b6d19ccf69ba9, headf5de0ed1d20b2e2206520fd3d885e52835aeb67c): my own reproduction tests against both shas, the package vitest suite at head and against base source, andnpx tsc --noEmitat head. NoFORGEJO_API_KEYread or used; no write other than this comment.Verdict of record — ld-reviewer — head
f5de0ed1d2: APPROVEmodel: omniroute/deepseek-flash
session: 01a1b3ef-7e5e-70d7-89c1-cc2d6a74a33a
Blockers
None.
What I measured (independently; seat-2 worktrees, own probe file)
1. Diff scope.
git diff aa6d0097 f5de0ed1= 2 commits, 4 files, +44/−1:forgejo.ts(isPending()gainspending+ 3 comment lines),index.ts(STATUS_ICON.pending = "⏳"), the two test files. Nothing beyond the pending leg.f5de0edalone is an empty CI-retrigger commit.2. Before/after on the claimed scenario. Own probe (
tests/s2probe.test.ts, mine, deleted after), runstatus: "running",actions/tasks→blank500(), commit statusesbuild-and-push: success/e2e: pending:aa6d0097:run #1481 c7a74c7d4a #1665 [pass]and• e2e pending ?f5de0ed1:run #1481 c7a74c7d4a #1665 [running]and⏳ e2e pending ?The false pass is real on base and gone on head.
3. Completeness of the pending leg (both spellings sets, read at source):
/api/v1/version=14.0.3+gitea-1.22.0),services/actions/commit_status.gotoCommitStatus()mapswaiting|blocked|running → pending,success|skipped → success,failure|cancelled → failure,default → error. Sopendingis the only unfinished spelling Forgejo's actions writer ever emits here — it was the whole gap on this leg, and it is now covered.CommitStatusStateswagger doc agrees (pending|success|error|failure|warning).models/actions/status.go:unknown|waiting|running|success|failure|cancelled|skipped|blocked), fed byactions/tasksand by the run page (ViewJob.Status = job.Status.String(),routers/web/repo/actions/view.go). Unfinished =waiting,running,blocked; all three were already inisPending()and I re-verified each renders[running]in all three sources (tasks,run-page,commit-status).unknownis Forgejo's zero value and is not reachable on either feeding path:models/actions/run.goinserts every run job withStatusBlocked | StatusWaiting | StatusFailureonly (nil/unparseable job payload), andtoCommitStatussendsunknowntoerror(renders[FAIL], neverpass). So no unfinished, actually-emitted spelling still renders pass. Observation only (not a blocker):isPending()still omitsunknown, whichactions.PendingStatuses()does list — a defensive addition if you want belt-and-braces, unreachable today.4. Behaviour beyond pending. My probe sweeps 21 scenarios (10 commit-status spellings, 6 task spellings, 5 run-page spellings) on base and head;
diffof the two outputs changes exactly 4 rows —CS pending,CS PENDING,TASKS pending,PAGE pending— header[pass] → [running]plus icon• → ⏳. No other state's header or icon changed, no ordering change,error → [FAIL],warning|skipped → [pass]unchanged on both sides. Grep confirmsisPendinghas exactly one consumer (index.ts:265,renderStatus), so the PR's "nothing else consumes isPending" holds.STATUS_ICONis read only byicon()(no legend iteration), so the new entry cannot affect other states.5. Tests at head (worktree of
f5de0ed1, packagenode_modulessymlinked):npx vitest run→Test Files 3 passed | 1 skipped (4),Tests 54 passed | 11 skipped (65); same suite on base →52 passed | 11 skipped (63)(delta = the 2 new tests).npx tsc --noEmit→ exit 0, no output.6. CI on the head (read via
forgejo_ci_status, filter#116): run #406 onf5de0ed1d2, all 15 jobssuccess, run header[pass].Exposure
Read: local git objects base
aa6d00972ad9e4d2df4e9c68f24b6d19ccf69ba9and headf5de0ed1d20b2e2206520fd3d885e52835aeb67c(diff, both test files,forgejo-ci/{forgejo,index,types}.ts), forgejo 14.0.3 source at tagv14.0.3(models/actions/status.go,models/actions/run.go,routers/web/repo/actions/view.go,services/actions/commit_status.go), the instance swagger (CommitStatusState), live/api/v1/version. Ran: my owntests/s2probe.test.tsin two detached worktrees (/tmp/s2-116/base,/tmp/s2-116/head), the package suite andtsc --noEmitin both,forgejo_ci_statusfor PR #116. I did not modify the PR branch, did not run a live pending run, and posted nothing but this verdict.