Repository navigation
Description
The board under-reports attempts by up to 9×, points "Needs you" at ended sessions, and carries neither reason nor timestamp
Measured live 2026-08-15 across 83 cards on 5 boards.
1. attempts is 1 on all 83 cards, contradicting the board's own note
The board states: "retries of the same job collapse into one; attempts = run count". Counter-evidence from check_instance_loop on the same instances:
| instance | card | runs carrying that sessionId |
card |
|---|---|---|---|
f8ddc272 |
csess-csess_c960d431… |
9 (9f3cf5ab, 026d4255, f19d0654, 0dce25fb, 2ab928b6, 86a7ad7f, ea27fbfb, 0cba259e, c7a03659) |
attempts:1, completed |
bd43f4de |
csess_c306e923 |
3 (one needs_human, one completed, one failed) |
attempts:1 |
e4d2d031 |
csess_9228b52f |
2 | attempts:1 |
One of the nine (9f3cf5ab) failed. The board shows a single clean success where nine runs happened, one of them a failure.
2. Four of five "Needs you" cards point at sessions that ended
| instance | card | card status | session | ended |
|---|---|---|---|---|
e4d2d031 |
csess-csess_88a4de4b… |
needs_human | ended | 09:29:44 |
f8ddc272 |
csess-csess_f7a6fcec… |
needs_human | ended | 09:27:41 |
e26f69d0 |
csess-csess_f6266368… |
needs_human | ended | 09:29:01 |
5219a03a |
csess-csess_e94eb26a… |
needs_human | ended | 09:28:22 |
bd43f4de |
csess-csess_c306e923… |
needs_human | active (capture-verified) | — |
Both halves are true in isolation — the run needs a human, the session is over — but the card is keyed on the session (jobKey: csess-<id>) and its affordance is "take over" something closed. board.ts:334 is write-through with no read-time join; closeCodingSessionCards' openOnly:true guard is correct for failed/completed and leaves needs_human pointing at a dead session.
3. The card that asks for help carries no reason and no timestamp
All 5 needs_human cards have detail: "" while the run carries the full explanation ("the limit does not reset until 2026-08-17 16:00 +10:00 … beyond the 6 hours a run may wait"). No card on any of the 5 boards carries a timestamp.
Concrete cost: e4d2d031's "Waiting"/queued card task_fbf2744e… is undated on the board; instance_task_events dates it 2026-08-15T08:55:25.916Z — queued 1h13m before the audit and, given the engine limit, unable to run until Aug 17. Neither fact appears where the owner looks.
Why this misleads into a wrong action
The board is the "does anything want me?" surface (#553). It currently asks for attention without saying why, offers to take over something that no longer exists, and hides that a job was retried nine times with a failure among them.
Acceptance criteria
attemptsreflects the run count, or the note stops claiming it does. Verify against a card with a known multi-run session —csess_c960d431has nine.- A
needs_humancard whose session has ended either resolves, or states that the session is over and offers the action that is actually available. - A card asking for human attention carries the run's reason and a timestamp.
- A guard asserts board card state agrees with the run/session it is keyed on, denominator stated per ADR 0002 over all cards on a fixture board — not one card.
- Red demonstrated on the nine-run case.
Verified vs inferred
- Verified: all counts and card values read live;
board.ts:334; the nine run ids; the task-event timestamp. - Inferred: that
attemptsis never incremented anywhere. It reads 1 on 83/83 and the note says otherwise, but the writer was not traced — that is the first thing to establish.
Activity
fix(board): a coding card reports the runs behind it, stops asking fo…
serge-ivo commentedon Aug 15, 2026
Shipped in
37bb2ea(onmain, CI + API/MCP/Host deploys all green).The traced answer to the
attemptsquestionThe issue labelled "attempts is never incremented" as inferred and asked for the writer to be
traced first. There is no writer, and nothing was ever incremented —
attemptsis not a counter.buildInstanceBoardgroups the instance'sinstance_runtime_tasksbyjobKeyForTaskand reportsarr.length(board.ts:335), soattemptscounts card rows sharing a job key, not runs.collapse on the normalized URL.
board-config.test.ts:295already pinned that at 2, so the notewas true for that family all along.
it:
codingCardIdiscsess-<sessionId>(coding-board.ts:52) andupsertWorkCardisON CONFLICT(id) DO UPDATE(work-card.ts:22) — one session is one row forever. That dedup isthe feature ("so every transition upserts the SAME row rather than piling up").
A coding session's attempts are its
agent_loop_runs, in a different table, which the board neverread. So the fix is not a write — it is the read-time join
board.ts:334never had, which is alsothe only form that can be correct: a session leaves
activein five places (enumerated incoding-board.ts) and a write-through would have to be added to all five and kept there.What changed
lib/board-runs.ts(new) joins each coding card to its runs and its session at read time, percoding-board.ts's own rule that the run is the authority (#553).attemptsis the runs on that session, newest first. A hand-driven session with noloop run keeps its single row-derived attempt ("one session, no automated runs" is the truth
there). The MCP note now says
attempts= the runs behind it, which is finally accurate forboth card families.
needs_humancard whose session is over stops asking. Where the run has sincereached a verdict, the card adopts it. Where the run is also still parked, nobody ever
answered, and the card becomes
failed— nevercompleted, which is the exact overwritecloseCodingSessionCards'openOnlyguard exists to prevent. The fifth card (csess_c306e923,capture-verified active) is untouched, as is a card whose session row is missing: an absence is
not a death. Cards also carry
sessionEnded: trueso a reader can say the takeover is gonewithout inferring it from a status. No console change was needed — leaving
needs_humanis whatwithdraws the takeover affordance.
detail: ""becausesetCodingSessionCardStatuspatches only$.status); a live progress line (#207B) is neveroverwritten. The missing timestamp was an MCP projection loss, not a board one:
BoardItemView.updatedAtexisted all along andgroupBoard's fixed card list dropped it. Added.board-runs.test.tsasserts the invariant over a 9-session fixture covering everycombination observed live, with the denominator asserted and printed per ADR 0002 (G1/G2) and
unresolvable cards reported rather than skipped (G3).
attempts 1 ≠ 9 runsoncsess_c960d431, fourneeds_human on a ended session, and the nine-run card returning oneattempt.
No
MCP_SERVER_VERSIONbump: the surface fingerprint excludes descriptions and covers no responsebody, and neither tool declares an
outputSchema.Verified / not verified
Verified: full suite green; the new guard red-then-green on the nine-run case; typecheck,
biome, file-size, bare-catch, surface-lock, migrations, test-isolation, design-tokens,
agents-allowlist, qa-config, docs-drift; Playwright 405 passed (chromium + webkit); CI and all
three deploys green;
api.proagentstore.online/healthandmcp.proagentstore.online/healthlive.NOT verified in production: I hold no session token, and both the board API and the MCP surface
are auth-gated, so I could not read
csess_c960d431live to confirm it now reports nine attempts,nor confirm the four
needs_humancards have left the column. Leaving this open for that onecheck — it needs an authenticated read (
instance_boardonf8ddc272,e4d2d031,e26f69d0,5219a03a,bd43f4de), which thepags-liveagent can do.serge-ivo commentedon Aug 15, 2026
Verified live — the check
37bb2eacould not make without a session tokenRead from production 2026-08-15 on
f8ddc272(Heartfull), 24 cards:attemptsoncsess_c960d43119— the exact nine runsattemptsoncsess_e4775bf712updatedAtneeds_humanon an ended sessiondetailon the help-asking card""sessionEnded: trueis now on the card, and the board note documents it. All three defects in this issue are closed on live data.One residual contradiction the new data exposes
Two cards sit in Done with
status: "completed"and adetailthat says the run failed:csess_c960d431—status: completed,runStatus: completed,attempts: 9, detail: "outcome: failed — run error: UserAiProviderError: The AI provider stopped sending mid-reply…"csess_e4775bf7—status: completed,runStatus: completed,attempts: 2, detail: "outcome: failed — stuck not resolved in time"With
attempts: 9this may be correct in substance — eight runs succeeded and the last one failed — but the card presents a column and a status saying success beside prose saying failure, and a reader cannot tell which run the detail describes.The audit established that one of
csess_c960d431's nine (9f3cf5ab) failed. So the open question is narrow and worth answering explicitly rather than by inspection: when a job has N runs with mixed outcomes, which one doesdetaildescribe, and shouldstatusreflect the last run, the best, or the fact that outcomes were mixed?This is the same class this issue fixed — a summary that cannot express what actually happened — one level in. Recommend it stays open on that point, or is split out if the answer is a design decision rather than a fix.
Verified vs inferred
9f3cf5abwas confirmed failed; the other eight outcomes were not individually re-checked, so "mixed" is established but the ratio is not.feat(guards): every console capability is reachable over MCP, deliber…
fix(board): a card speaks with its own run's voice and counts the res…
serge-ivo commentedon Aug 18, 2026
Shipped in
de46d2a7. CI green, API + MCP deploys green,/health200 on both. Leaving this open for one authenticated read — see the bottom.The residual, answered
The last comment left one question open: "when a job has N runs with mixed outcomes, which one does
detaildescribe, and shouldstatusreflect the last run, the best, or the fact that outcomes were mixed?" It was a real contradiction — two cards in Done,status: "completed", carrying prose that said the run failed.The mechanism, traced rather than guessed. Both halves came out of
reconcileCodingCard, from two different runs.runStatusis the card's own, written by the run that owns it;descriptionwas borrowed unconditionally fromruns[0], the newestagent_loop_runsrow. Nothing required them to describe the same run. And the card's own run is often not in that table at all:POST …/coding/sessions/:sid/run(routes/coding.ts:688) creates the Workflow with noloopRunId, so a session driven that way writes the card and leaves no run row. That is also why the obvious fix is wrong — "adopt the newest row's status" would let a stale historical run overwrite a card a live, row-less run had just written.The rule now:
detaildescribes the run that AGREES with the card's status. A card quotes its own run or it quotes nothing, so the column and the prose cannot contradict each other. One exception, and it says so on the card: the run aneeds_humancard was waiting on when its session died — why it was waiting is the explanation for that card.statusstays where it is, for the reason above.9 runs: 8 completed, 1 failed— leading the detail, percard-detail.ts's rule from A board card's detail is cut at exactly 300 chars mid-word with no ellipsis — 13 of 15 irreversible acts vanish, and the full text is already on the same card #568 that the count goes first and is never what gets cut. Emitted only when the runs disagree; a tally on every card is noise on the many to serve the few.So
csess_c960d431can no longer read as one clean success, and it can no longer read as a failure either: it reads as nine runs, eight of them completed and one failed, with its own run's account beside them.Verified / not verified
Verified: 9,827 tests; typecheck (11 projects); biome;
check-file-size,check-migrations --require-history,check-surface-lock --require-history,check-mcp-parity,check-bare-catch,check-test-isolation,check-design-tokens,check-agents-allowlist,check-qa-config;docs:drift;docs:build. Red demonstrated with the join removed — 6 failing, including the fixture guard. The board invariant now measures rule 4 over the whole fixture and states its own denominator per ADR 0002 (4 cards stand for runs that ended differently, asserted, because that arm could otherwise pass over zero cards).NOT verified in production. The board is auth-gated (
/v1/instances/…/board→ 401 without a session) and confirming it live would mean reading — and in the failure case moving — the owner's real cards. So the two live cards this fix is written from were not re-read.pnpm test:e2eskipped: the diff touches nostore/oragents/coder/web.The one check left, for an authenticated reader (
pags-livecan do it):instance_boardonf8ddc272, cardscsess_c960d431andcsess_e4775bf7. Expected now — the detail no longer containsoutcome: failedunder acompletedstatus, and leads with9 runs: 8 completed, 1 failed/2 runs: 1 completed, 1 failed. If that reads right, this can close.Verified vs inferred
routes/coding.ts:688creates the workflow with noloopRunId;reconcileCodingCard's two sources; every gate above./runpath is what produced the specificcompletedoncsess_c960d431. It is a path that produces exactly this shape; which of the nine runs wrote that status was not established, and the fix does not depend on it — the invariant is that the two halves agree, whichever writer won.serge-ivo commentedon Aug 23, 2026
DELIVERED — closing. Verified in code at
HEADand re-measured live on the same board this ticket was filed from.Commits:
37bb2ea1(the read-time join) andde46d2a7(one card, one run's voice).Each acceptance criterion, against
mainattemptsreflects the run count, or the note stops claiming it doesworkers/api/src/lib/board-runs.ts:1-20states the finding this ticket asked for — "attemptswas never a counter and nothing ever incremented it …buildInstanceBoardgroups the instance'sinstance_runtime_tasksbyjobKeyForTaskand reportsarr.length" — and replaces it with the read-time join atboard.ts:375(reconcileCodingCard). Pinned byboard-runs.test.ts:44"reports nine runs for the nine-run session, with the failure among them (#592 case 1)"needs_humancard whose session ended resolves, or says the session is overboard-runs.ts:106/** Said when the session died under a card that was still asking for a human. */; tests atboard-runs.test.ts:90,100,114, and the control at:124— "leaves a card asking while its session is still active — that one really does want you"board-runs.test.ts:144"fills an empty detail from the latest run (#592 case 3)", with:154guarding that a live progress line is never overwritten (#207B). Timestamp:board-runs.ts:193—runs.map((r) => ({ id: r.runId, status: r.status, updatedAt: new Date(r.at).toISOString() }))board-runs.test.ts:369describe("board invariant — every coding card agrees with the run and session it is keyed on")→:370"holds over the whole fixture board, and says how many cards it measured"board-runs.test.ts:448"the nine-run card reports nine attempts and its failure, end to end";:456"the four ended 'Needs you' cards leave the column; the active one stays"Measured live today, not inferred
GET /v1/instances/f8ddc272-0390-4826-8812-94989e3d2ebd/board— the same instance and the same sessioncsess_c960d431:All nine run ids are the ones named in the ticket's own table, the failure is among them and named, and every attempt carries a timestamp. This is the exact case the title was written from — "attempts:1 for a job that ran 9 times" — and it now reads 9.
Case 2 is also live on the same board: card
csess-csess_f7a6fcec-…now opens with "The session ended while this was waiting for you — there is nothing left to take over."Verified: every
file:lineabove; the live board response quoted verbatim. Inferred: nothing — both halves of the finding were re-measured against production rather than read off the test suite.feat(board): reconcile every durable application onto the Board, and …