Repository navigation
fix(pr-fix): settle worker-reported needs-human endings and release settled hand-outs (#1340) - #1345
itsmiso-ai wants to merge 6 commits into
Conversation
…ettled hand-outs (#1340) A bare blocked report parks the attempt, but a run that ends needing a human decision must land BLOCKED/human_decision, not a silent park: - A non-operator BLOCKED mark may now escalate a same-generation PARKED row (the one direction the #1218 sticky pin relaxes); de-escalation stays operator-only. - BLOCKED settlements always carry a decisionDetail, synthesized from the mark/report diagnostic when no structured decision is supplied. - A worker blocked report that settles the attempt releases the reporting agent's hand-out/ack/receipt entries at the settled generation, clearing dispatch stamps only when the last live hand-out is gone (mirrors #1256).
…ts (#1340 review) - releaseAgentHandout re-reads and pins the exact handout/ack/receipt arrays it decided on, retrying once; a concurrent hand-out or sweep can never be clobbered by a stale snapshot. - A blocked report releases the reporting agent's records even when the settlement skips (row already settled), generation-pinned so a re-issued attempt is never touched. - Synthesized decisionDetail evidence is bounded to MAX_EVIDENCE_LENGTH.
joryirving
left a comment
There was a problem hiding this comment.
Secondary review on exact head f81c101b13525714f93dc47382b5659394f9cfe5: REQUEST CHANGES only to preserve the primary-review loop. The primary's one minor finding is legitimate: the new equals / in branch in src/lib/pr-fix-queue-evidence.test.ts is over-indented relative to the surrounding operator chain. It is cosmetic and I found no functional hard blocker, but per our review policy I am not dismissing a technically-correct primary finding just because it is a nit. Please normalize that indentation; no behavioral change requested. Exact-head CI/security/smoke/image checks are green.
…est fake (#1340 review)
|
Addressed the review feedback on head Finding (primary + secondary): the new Fix: Local validation (cwd
CI on the new head is left pending for operator verification. |
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M3.1-Flash (anthropic) — primary route · pr-reviewer-action v3.7.0
Findings (1 major)
| Severity | Location | Finding |
|---|---|---|
src/lib/pr-fix-queue.ts:3426 |
The release filters only by agent and generation, so a stale same-generation report can remove a newer hand-out instead of requiring the issued `handoutToken` as required by PR 1256/PR 1340. |
Request changes: the new hand-out release is not fenced to the issued per-hand-out token, so the PR does not satisfy PR 1340’s explicit release contract.
src/lib/pr-fix-queue.ts:3426filters receipts, hand-outs, and acknowledgements only by reporting agent and generation. A stale same-generation report can therefore remove a newer/reissued hand-out for that agent instead of requiring the issuedhandoutToken, unlike the capacity-exhaustion release.- PARKED → BLOCKED escalation, sticky terminal states, bare-block/no-progress handling, and synthesized
decisionDetailare internally consistent in the inspected paths. - Claims checked: 3/5 held; 2/5 hand-out claims were violated by the missing per-token fence.
Linked Issue Fit
The BLOCKED/human_decision outcome, populated decision detail, PR visibility, and PARKED escalation satisfy the fix-pr portion of PR 1340. The hand-out release does not use the issue-required same fenced per-token release. The resolve-issue status/blocked acceptance criterion and fresh-evidence requeue path were not independently re-verified because the investigation budget was exhausted.
Tool Harness Findings
The harness exhausted its 24-call budget after verifying the queue writer, report resolver, worker-identity gate, report route, hand-out parsers, and relevant callers. Uninspected paths remain unverified rather than presumed safe.
Outstanding Human Change Requests
- @joryirving's change request (f81c101, head moved since) judged addressed at this head:
src/lib/pr-fix-queue-evidence.test.ts:57-60 now aligns the `equals` and new `in` branches with the surrounding operator-chain indentation.
… token (#1340 review) The AI review on head 7315a67 flagged that releaseAgentHandout matched only (agent, generation), so a stale same-generation report could remove a newer re-issued hand-out — issue #1340 asks for the same fenced per-token release as the #1256 capacity handoff. - When the report echoes the issued handoutToken, the release is authorized only by an exact live (agent, generation, token) receipt match (via findHandoutReceipt, same as the capacity path); a rotated/released/unknown token releases nothing. - Reports without a token (Courier blocked reports do not echo one yet, internal/source/dispatch/client.go echoes it only for capacity-exhausted) keep the (agent, generation) fallback but ONLY against a non-QUEUED row: on a still-dispatchable row the settlement did not land, so an unfenced release could clobber a live hand-out. - All three call sites pass attempt.handoutToken; policy doc updated. Tests: exact-issuance release (other agent's hand-out + stamps survive), stale token refuses with zero mutation, no-token fallback skips a still-QUEUED row. Existing fallback tests now advance the mock row through the settlement write (the release re-reads it). Full local validation: vitest 5106 passed, typecheck clean, lint 0 errors.
…kip path (#1340 review) Closes the review's test-gap minor: the exact (agent, generation, token) authorization is exercised on the skip-site release as well — zero settlement writes, other agent's live hand-out and stamps survive.
|
Addressed the major AI-review finding on head Finding (major): Verified before fixing: the finding is valid — issue #1340 explicitly asks for "the same fenced per-token release as capacity-exhaustion settlements (#1256)". But a strict token requirement alone would silently fail the issue's own acceptance: Courier main ( Fix (
New tests: exact-issuance release (other agent's hand-out + stamps survive), stale token refuses with zero mutation, no-token fallback skips a still-QUEUED row, and the fenced release on the already-settled skip site. Independent review: one pass over Local validation (cwd
CI on the new head is left pending for operator verification. |
There was a problem hiding this comment.
AI Automated Review
Full PR review.
Analysis engine: MiniMax-M3.1-Flash (anthropic) — primary route · pr-reviewer-action v3.7.0
Partial coverage: required-check coverage is incomplete — this review did not resolve every required check and must not be read as a complete pass.
Findings (1 major)
| Severity | Location | Finding |
|---|---|---|
src/lib/pr-fix-queue.ts:3499 |
Token-less blocked reports use an unfenced agent/generation release instead of requiring the exact live per-hand-out receipt mandated by PR 1340. |
Request changes: the settled hand-out release is not always fenced to the issued per-hand-out token, contrary to PR 1340’s explicit release contract.
- Major:
releaseAgentHandoutaccepts token-less reports and falls back to releasing every matching agent/generation entry (src/lib/pr-fix-queue.ts:3499). Although restricted to non-QUEUEDrows, this is not the same fenced per-token release required by issue PR 1340 and the requirement ledger; require the exact live receipt for settlement releases. - Claims: violated 1 of 5 claims—the hand-out-release fence; the inspected escalation, decision-detail synthesis, and bare-
blockedbehavior otherwise held.
Linked Issue Fit
- The fix-pr
BLOCKED/human_decisiontransition, populateddecisionDetail, needs-human surfacing, and requeue behavior are implemented and covered by the changed tests. - The release path misses the issue’s required per-token fence. The separate resolve-issue acceptance path was not independently verified in this repository.
Tool Harness Findings
- Twenty-four read-only calls verified the status-filter write sites, report authorization path, receipt matcher, CAS release, surface behavior, and relevant tests.
- The tool budget was exhausted before an exhaustive caller audit; no additional failing path was established, but the remaining unexamined callers are unverified.
Unknowns or Needs Verification
- Courier’s external report construction and resolve-issue status handling were not directly inspectable in the Dispatch repository; those paths remain unverified.
Sources: src/lib/pr-fix-queue.ts, src/app/api/agents/[agentName]/tasks/report/route.ts, src/lib/pr-fix-surfacing.ts, and the changed PR-fix queue tests.
Unaddressed required checks
The classifier marked these checks as required for this PR's risk profile, but the review does not resolve or disposition them:
- treat as high priority — verify correctness carefully
Outstanding Human Change Requests
- @joryirving's change request (f81c101, head moved since) judged addressed at this head:
The current head normalizes the `equals`/`in` operator-chain indentation at src/lib/pr-fix-queue-evidence.test.ts:58-59, matching commit 7315a67 and resolving the cosmetic request without behavioral changes.
|
Re-verified the open AI-review major on head Verified against Courier main
So for Courier's actual NeedsHuman flow today the token branch ( Why the gated fallback cannot strand or clobber a live generation (the residual concern in the finding):
Path to the fenced-only contract: misospace/courier#516 makes Courier echo @joryirving — your own change request (the over-indented Local validation (head
No code change on this pass; CI on |
|
Courier: already addressed (run courier-1f3b7cd9f02f7211a2d6b5e8d318a6f1) PR #1345 (issue #1340) is already implemented, pushed, open/non-draft at head 555cb0a (local==origin, clean tree). The only open item is a repeated AI-review CHANGES_REQUESTED (major) asking that the token-less hand-out release in src/lib/pr-fix-queue.ts:3499 require the exact per-hand-out token like #1256. Verified against current source before acting: Courier main f12168a internal/source/dispatch/client.go sends prFixItem.handoutToken ONLY for outcome=capacity-exhausted (client.go:906-907); a NeedsHuman fix-pr (ResultBlocked) sends tasks/report outcome=blocked with no token (client.go:1018) and a BLOCKED mark with no token (client.go:837-840). So a strict-token-only release would leave Courier NeedsHuman hand-outs UN-released, regressing #1340's own acceptance ('the reporting hand-out is released'). The current design fences exactly on (agent,generation,token) whenever a token is present (findHandoutReceipt, same matcher as the #1256 capacity release) and, for token-less reports, releases only a non-QUEUED (settled, inert) row, worker-identity-bound and CAS-pinned, preserving other agents' hand-outs and clearing stamps only on the last live hand-out — it cannot unlock a live QUEUED generation for re-dispatch (covered by the QUEUED-row skip test). misospace/courier#516 tracks making Courier echo the token so the fenced path becomes the only path. The assigned human reviewer (joryirving)'s only request (indentation in src/lib/pr-fix-queue-evidence.test.ts) was resolved at 7315a67 and the AI reviewer confirms it addressed at this head. Posted an evidence-backed PR comment (issuecomment 6338452875) rebutting the bot major and flagging the migration-window tradeoff for joryirving. Local validation on this exact head (fresh npm ci, node v24.22.0): targeted pr-fix suites 11 files/387 tests passed; npm run typecheck clean; npm run lint 0 errors (1 pre-existing login/page.tsx warning, untouched). CI on 555cb0a is green across all jobs. Left open for operator verification. |
Note: requeue from queue page Updated automatically by Dispatch on 2026-10-22T14:29:42.092Z |
joryirving
left a comment
There was a problem hiding this comment.
Secondary re-review on exact head 555cb0acde18516613913f7e84b9c58cb1e9656d: REQUEST CHANGES for the current primary finding. It is legitimate. Issue #1340 explicitly requires releasing the reporting hand-out with the same fenced per-token release as capacity-exhaustion. releaseAgentHandout does that when handoutToken is present, but deliberately falls back to (agent, generation) on settled rows when it is absent. That fallback is bounded and avoids clobbering a still-QUEUED hand-out, but it is still not exact-issuance fencing and therefore does not satisfy the requested contract.
This is a cross-repo sequencing problem rather than a request for more local cleverness: Courier #516 is already open to echo prFixItem.handoutToken on every followup-pr report. Land that producer change, then make this settlement path require the exact live receipt (or otherwise prove an equivalent exact-issuance fence) and add the corresponding regression. Do not regress the core #1340 behavior: a NeedsHuman settlement must still move the item out of QUEUED and release its completed issuance so it cannot head-of-line block the lane.
The earlier indentation nit is fixed, and the PARKED→BLOCKED / human-decision behavior otherwise looks sound. Exact-head CI/security/smoke/image checks are green.
What
A Courier run ending terminal
NeedsHumansendstasks/reportwithoutcome: "blocked"(noblockReason) and then a worker-tierPOST /api/pr-fix-queue/markwithstatus: "BLOCKED". Before this PR the report parked the itemPARKED/no_progress, the follow-upBLOCKEDmark was refused by the #1218 sticky status pin (worker marks pinned the current status toQUEUED), and no path ever released the reporting agent's hand-out records — the item surfaced nothing and the consumed hand-out stayed live.Changes (all in
src/lib/pr-fix-queue.ts+ docs/tests):BLOCKEDsettlement (workerblockedreport withblockReason: human_decision, or a directBLOCKEDmark) may now land on a same-generationPARKEDrow, escalating it toBLOCKED/human_decisionwith the needs-human label + marker (existingsurfacePrFixBlockedmachinery folds the parked marker in place).PARKEDmarks stayQUEUED-only; recordedBLOCKED/FIXED/STALErows stay sticky against worker marks; de-escalation stays operator-only viaallowStickyReclassify.decisionDetailis always populated onBLOCKED. When no structureddecisionis supplied, Dispatch synthesizes{question, evidence}from the mark/report note (fallback: itemreason), bounded toMAX_EVIDENCE_LENGTH. Structureddecisionpayloads pass through unchanged.releaseAgentHandoutdrops the reporting agent'sagentHandouts/handoutAcks/handoutReceiptsentries at the settled generation when ablockedreport settles the attempt — or when the settlement skips because the row already settled. When the report echoes the issuedhandoutToken, the release is authorized only by an exact live(agent, generation, token)receipt match (findHandoutReceipt, same matcher as the feat(recovery): add fenced capacity-exhaustion handoff #1256 capacity release): a rotated/released/unknown token releases nothing, so a stale same-generation report can never remove a newer re-issued hand-out. A report without a token keeps the(agent, generation)fallback — verified necessary: Courier main (internal/source/dispatch/client.go,reportTaskWithPR) echoeshandoutTokenonly forcapacity-exhaustedreports, so requiring it unconditionally would mean Courier's NeedsHuman hand-out is never released, failing this issue's acceptance. The unfenced fallback applies only against a non-QUEUED row (where entries are inert — they gate dispatch only while QUEUED), never to a still-dispatchable row. It re-reads the row and CAS-pins the exact arrays it decided on (mirrors the feat(recovery): add fenced capacity-exhaustion handoff #1256 capacity release), retries once on a pin miss, and is best-effort. Dispatch stamps (dispatchedGeneration/dispatchedAt/attemptEvidenceWatermark) clear only when the release removes the last live hand-out; other agents' live hand-outs survive with their stamps. Tightening courier-side tracked in echo prFixItem.handoutToken in every followup-pr tasks/report, not only capacity-exhausted courier#516.Resolves the fix-pr half of #1340. The resolve-issue half (issue →
status/blockedwith a populatedblockedReason) is already enforced today: Courier's NeedsHuman terminal callsPOST /api/issues/statuswithstatus: blocked+blockedReason, which this PR leaves untouched.Invariants this change must keep — and every other path enforcing the same rule
blockedreport withoutblockReasonmeansno_progress, never a falsehuman_decision(fix(pr-fix): separate human decisions from retry exhaustion #1218). Kept unchanged: the resolver still parks; only an explicitBLOCKEDmark/report escalates, and only against same-generationPARKED.BLOCKED/PARKEDcannot be erased/downgraded by a worker second mark (fix(pr-fix): separate human decisions from retry exhaustion #1218). Kept:PARKEDmarks andFIXED/STALEnon-maintainer writes staystatus: "QUEUED"-pinned;BLOCKEDgains onlyPARKEDin its allowed set (escalation into the stickiest state, never out). Writer: the singlestickySettlementStatusFilterinmarkPrFixItem, applied at all six guarded writes (fast path, keys-cleared loop, sticky force, force tail, refused-FIXED, reopen). Operator reclassify stays gated onactorTier === "maintainer"+allowStickyReclassify(route-sourced, never body-forgible).BLOCKEDescalation still passes through the keys-pinned writes; aPARKEDrow with fresh keys escalates via the keys-cleared/sticky-force paths and clears them (new test).agentHandoutsentry exists (feat(recovery): add fenced capacity-exhaustion handoff #1256). Kept:releaseAgentHandoutfilters all three arrays in one pinned write; every other writer (next-taskissuance, reclaim sweep, capacity release, fresh-attempt /QUEUED/WAITING_EXTERNALmarks) still clears them together — untouched.(agent, generation, token)receipt authorization (same entry parsers as the capacity release); without one, the release cannot run against a QUEUED row at all, so it can never unlock a live generation for re-dispatch. The CAS pin means a concurrent hand-out/ack/sweep makes the release a no-op instead of clobbering. A lost release race leaves entries inert — they only gate dispatch whileQUEUEDat that generation, and any generation change (requeue, fresh evidence) clears them.BLOCKEDtwo-way (needs-human label and BLOCKED marker survive when a PR-fix item leaves BLOCKED via mark or new evidence #1105/fix(pr-fix): separate human decisions from retry exhaustion #1218). Unchanged:requeuePrFixItem, mark-to-QUEUED, and cap/refusal parks still clear outcomes, retract the needs-human label, and bump the generation.WAITING_EXTERNALhas a single status writer (feat(pr-fix): persist authoritative evidence versions and attempt-snapshot freshness (#1249) #1276). Untouched.BLOCKEDsurfacing inmarkPrFixItem.finishMarkedonly.Local validation (head 555cb0a, cwd /workspace, clean tree)
npx vitest run src/lib/pr-fix-queue-report.test.ts src/lib/pr-fix-queue.test.ts src/lib/pr-fix-queue-evidence.test.ts src/app/api/pr-fix-queue— 386 tests passed, 0 failednpx vitest run(full suite) — 202 files / 5106 tests passed, 24 skipped, 1 todo, 0 failednpm run typecheck— cleannpm run lint— 0 errors (1 pre-existing warning insrc/app/login/page.tsx, untouched)Limitations: no live PostgreSQL/integration run here (unit fakes cover the queue semantics); end-to-end Courier NeedsHuman behavior is verified against
misospace/couriermain at fix time (internal/source/dispatch/client.goblocked-report + BLOCKED-mark sequence, and the token echo being capacity-only — hence the documented fallback + misospace/courier#516), not against a running deployment. CI is left pending for operator verification.Review
Three review rounds, all on record:
bdf48ad; its findings were fixed inf81c101(release CAS + skipped-settlement release + evidence bound + doc scope of "report or mark") and re-validated locally.f81c101raised one minor finding (over-indentedequals/inbranch insrc/lib/pr-fix-queue-evidence.test.ts); the secondary review requested that exact normalization. Fixed in7315a67— no behavioral change.7315a67raised one major: the release matched only(agent, generation), so a stale same-generation report could remove a newer re-issued hand-out instead of being fenced to the issuedhandoutTokenas fix(pr-fix): worker-reported NeedsHuman endings should mark the item needs-human, not leave it QUEUED #1340 requires. Fixed in7a0e8e6(+ test555cb0a): exact-receipt fencing when the token is echoed; the non-QUEUED-gated migration fallback for token-less reports, with the Courier-side verification above and echo prFixItem.handoutToken in every followup-pr tasks/report, not only capacity-exhausted courier#516 filed to remove the need for it. Independent review pass over7a0e8e6: approve, no blockers; its test-gap minor (fence coverage on the already-settled skip site) is covered by555cb0a.Closes #1340