fix(board): CI pill stuck on "CI running" after Forgejo workflow finishes #615
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks#615
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
User story
As an operator watching the planner board, I want the PR pill on a card to flip from
CI runningtoCI passed(orCI failed) the moment the workflow run completes on Forgejo, so I don't think a green run is still spinning and waste time investigating a phantom stall.Repro
Observed on board card for
charles/claude-hooks#605, PR #613 (refactor: hoist SSE…), 2026-05-01:🔄 CI runninglong after Forgejo Actions reports the run as finished.GET /api/v1/repos/charles/claude-hooks/actions/runs?head_sha=6ceaa8240503efea4d702e3b4bb0c45929545ea3returns:The run is terminal (
status: "success") but the adapter does not recognise this shape.Root cause
apps/server/src/infrastructure/forge/forgejo-adapter.ts:125-145(toForgeWorkflowRun) only handles two shapes:status: "completed"+conclusion: "success"|"failure"|"cancelled"|"skipped"|...— the documented Forgejo / GitHub Actions shape.status: "queued" | "in_progress" | "waiting"— pre-terminal.Anything else falls through to
"unknown". The Forgejo instance atforge.jacquin.app(v15.something) emits a third shape:statusalready folded to the conclusion ("success","failure", etc.) withconclusion: null. The adapter maps these to"unknown".Downstream, both folders treat
"unknown"as "still pending":apps/server/src/domain/views/board.ts:267-282foldCiState—else hasPending = true(line 276) → returns"pending"→ board pill stays onCI running.apps/server/src/domain/views/pipeline.ts:169-188foldWorkflowRuns— sameelse hasPending(line 182) → pipeline view also reportsrunningindefinitely.The PR-state cache in
board.ts:136(PR_CACHE_TTL_MS = 10_000) refreshes every 10 s, so this is not a staleness bug — every refresh re-fetches the same"success"payload and re-folds it to"unknown"→"pending".Acceptance criteria
Adapter (root fix)
apps/server/src/infrastructure/forge/forgejo-adapter.ts::toForgeWorkflowRunaccepts already-folded statuses as a third shape: whenraw.statusis"success" | "failure" | "cancelled" | "skipped"it passes through directly without consultingconclusion."completed"+conclusionshape continues to work unchanged."queued" | "in_progress" | "waiting"shape continues to work unchanged."unknown"— but"unknown"should be rare now, not the common case.github-adapter.ts:722) and GitLab adapter (gitlab-adapter.ts:828) only if they exhibit the same gap; verify by inspection, do not blindly mirror.Folders (defensive secondary fix)
domain/views/board.ts::foldCiStateanddomain/views/pipeline.ts::foldWorkflowRunsboth have anelse hasPending = truefor unknown. Document why (terminal-but-unrecognised → render as still running so we don't false-claim success). Decision is fine, but add an inline comment so a future reader does not flip it. No behaviour change here — the adapter fix is what actually resolves the bug.Tests
toForgeWorkflowRun({ status: "success", conclusion: null, ... })→status: "success". Repeat forfailure,cancelled,skipped."completed" + conclusioncases still pass.pipeline.test.tsalready has CI-fold coverage — extend to assert the new adapter-output shape folds tosuccesscleanly.successshape producespr.ci === "success"(not"pending").Manual verification
/planner/boardwith a card whose PR has a recently-finished workflow run.✓ CI passedwithin one cache cycle (≤ 10 s).CI failedcorrectly (find a PR with a failing run, or fake one in the API mock).Out of scope
container.lazy_*SSE events from M26-5 (#592) — unrelated to this card pill.References
apps/server/src/infrastructure/forge/forgejo-adapter.ts:125-145— adapter mapper, root site of the bugapps/server/src/infrastructure/forge/forgejo-api.ts:553-584—WorkflowRunSummaryshape +listWorkflowRuns(no change needed; the raw API can carry either shape)apps/server/src/domain/views/board.ts:267-282—foldCiStateapps/server/src/domain/views/pipeline.ts:169-188—foldWorkflowRunsapps/web/src/components/board/board-card.tsx:307-369— card pill rendering (no change needed)charles/claude-hooks#613head6ceaa8240503efea4d702e3b4bb0c45929545ea3resultevent #646