Uh oh!
There was an error while loading. Please reload this page.
Report the dead claim nobody can see: H27, and the post-kill recovery lines it points at - #11315
Merged
Merged
Conversation
A dev agent killed mid-task leaves a TEXTBOOK-correct claim: `pm:dispatched`, assignee set, a first-line `Claim:` comment, and a branch that exists on the remote. Every predicate in the patrol passed, so the card read as healthy in-flight work indefinitely — and worse than merely unreported: the next PM's round-open mutual-exclusion read treats a dead `Claim:` as a live claim by another session and stays off the card, so one dead agent blocks the lane. H20 is the near neighbour and misses it BY CONSTRUCTION, not by oversight: os-dev.md makes pushing the empty branch the FIRST action of the task, so a protocol-compliant agent that dies still leaves a ref and lands outside H20's no-ref-at-all population. The better the dev follows the protocol, the more invisible its death. H27 keys on the conjunction the filing card asked for: `pm:dispatched` ∧ a claim older than the protocol's OWN ~24h stale line ∧ a claimed branch that EXISTS ∧ that branch has not moved since the claim ∧ no PR delivers the card. The threshold is quoted from SKILL.md's 死认领回收 rule rather than measured — the fleet has 2 liveness samples, which is not a distribution. Costs no request: H27's 24h population is a strict subset of H20's 60-minute one, and the shared branch probe now reads `/branches/<b>` instead of `/git/ref/heads/<b>` — the same underlying ref, so H20's three states are untouched, but the head commit DATE rides in on a payload the sweep had already paid for. Report-only and pointedly NOT a reclaim: the protocol's reclaim rule applies to a branch that does not exist and states 「有带提交活分支的认领永不回收」, so this row prescribes the three-state recovery inspection and never an assignee drop. It deliberately under-reports the dev that pushed once and then died. Self-test 997 -> 1058 cases, both directions pinned per term. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
…te order (#11248) Two runbook lines the dead-claim patrol row needs a reader to have, plus the two DEFER lines the seat post carries — all four paid for by compressing in place, so the file lands back on its 243-line ceiling with no ratchet raise. 1. Post-kill recovery reads THREE states, not two. `git ls-remote` answers only "did it reach the remote"; agents in this fleet share one container, so a killed agent's worktree outlives the process and its uncommitted edits are usually still on disk. The middle state is the most valuable one and nothing looked for it: a PM that checks only the remote confidently tells the next dev "you are starting from scratch" on a false premise (measured — three files of real edits nearly discarded). Found work is handed on flagged UNVERIFIED — neither discarded nor trusted, because the surviving edits contained two defects, one of them a comment asserting a measurement never run. 2. Write order: flip the label only AFTER the claim comment succeeds. The two writes are not atomic, and a 502 on the comment with the label already flipped manufactures a genuine H2 (three measured instances in one round). Reversed order carries a retry obligation, and a failed retry rolls the label back. Folded DEFER lines: read `rate_limit` once before dispatch (the quota is a fleet-wide single point of failure — one limit took out three devs at once), and pnpm 10.31 rejects `install --workspace-concurrency` (the flag is only legal on script-running commands). Compression paid: the duplicated maintainer quote in the emergency-triage section (preserved verbatim in check-half-states.mjs's H13 docblock), one narrative aside about a separate line of work, and re-wrapping. No operative rule was dropped — verified with a word-level diff. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RMTpSRF5CjMmQBFfPtPCwJ
os-zhuang
self-requested a review
August 23, 2026 10:07
This was referenced Aug 23, 2026
hotlong
marked this pull request as ready for review
August 23, 2026 11:37
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#11248
Part of #11109
.claude/**, so it stays a DRAFT, merges by a human only, and must NOT be put on auto-merge or into a merge queue.What this adds
H27 — the claim is perfect and the claimant is dead. A dev agent killed mid-task leaves a textbook-correct claim:
pm:dispatched, assignee set, a first-lineClaim:comment, and a named branch that exists on the remote. Every predicate incheck-half-states.mjspassed on that shape, so the card read as healthy in-flight work indefinitely.Its danger is not that it goes unreported but that it reads as healthy: the next PM's round-open mutual-exclusion read looks for the latest non-self
Claim:on a lane's dispatched cards, so it treats a dead claim as a live claim by another session and stays off the card. The protocol's own mutual-exclusion mechanism turns one dead agent into a lane-wide block.The finding that shaped the design: H20 misses this BY CONSTRUCTION
H20 is the near neighbour and it keys on no remote ref at all.
.claude/agents/os-dev.mdrule 1 makes pushing the empty branch the first action of the task (a write-route probe, before any edit) — so a protocol-compliant agent that dies at any point after its first minute still leaves a ref, and lands outside H20's population entirely. The better the dev follows the protocol, the more invisible its death. That is not an oversight in H20; it is a consequence of a protocol step that is independently correct and worth keeping.So the two rows are disjoint by construction — H20 fires only when no branch resolves, H27 only when one does — and neither could be widened into the other without losing the property that makes it safe. Both directions are pinned on one fixture in the self-test.
The conjunction, and why each term is load-bearing
pm:dispatchedClaim:older than ~24hH20's docblock explicitly refuses to key on "no PR yet", for a measured reason: a dev inside a long build legitimately has a ref and no PR for over an hour. That objection is answered here by both remaining terms rather than by the threshold alone — a dev 24h in with commits landing is excluded by branch activity, and a dev with a PR open is excluded by delivery. What is left is a branch that has not moved since it was claimed, with nothing to show for a day.
The threshold is quoted, not measured. The filing card is explicit that the fleet has 2 liveness samples and that is not a distribution. So the row mechanizes SKILL.md's existing 死认领回收 line (「认领 >~24h」) — the same 24h the seat-post patrol already calls 「与既有回收线同一条」. One number in the protocol, two readers.
Report-only, and specifically NOT a reclaim
The protocol's reclaim rule applies to a branch that does not exist, and it states 「有带提交活分支的认领永不回收」. This row fires on branches that do exist, so it prescribes the recovery inspection and never an assignee drop — reporting a card is not reclaiming it, and the sentence says so in as many words. It also deliberately under-reports the dev that pushed one commit and then died (its branch moved after the claim): that is precisely the card the protocol protects, and the docblock states the gap rather than leaving it to be discovered.
Cost: zero extra requests
H27's 24h population is a strict subset of H20's 60-minute one, so every card it can speak about was already probed. The shared probe now reads
/repos/{o}/{r}/branches/<b>instead of/git/ref/heads/<b>— the same underlying ref, so H20's three states (exists/absent/unreadable) are untouched, but the head commit date rides in on a payload the sweep had already paid for. That is H26's own "FREE" shape. The summary line now reports the pair as serving both rows, so a reader seeing a quiet H27 knows what was read.statefield being computed identically. Everything downstream of the probe is pinned.The
Part of #11109half — premise falsified, and no row was addedThe dispatch asked for the patrol half of #11109 only if not already covered. It is already covered, by two merged rows, so this PR deliberately adds nothing for it. Adding a duplicate would be growth for no catch.
pm:blockedwhose body and comments carry noBlocked-by:lineh4BlockedNoBlockedBy(issue, commentBodies)reads both channels (since the #8941/#10061 widening) and returns the "NEITHER channel" sentenceBlocked-by:target already closedh19BlockOutlivedBlocker— "the block has outlived its blocker"The regression pins for #11109's exact shape are already in the self-test and were not written by this PR:
H4: neither channel -> finding,H4: an empty comment thread is a real reading, not an unconsulted one, plus the decorated/bulleted/bolded variants and the cross-repo case.I also re-measured the specimen the card was filed on, #11017: it is now closed, carries no
pm:*label, has noBlocked-by:in its body but does have one in a comment — i.e. exactly the two-channel shape H4 already clears. The card's premise ("this is one more predicate the patrol needs") was true when filed against a body-only reading and is no longer true against currentmain.⛔ #11109 remains open after this PR — its wording half lands separately in the wave-4-B fold, and that is the half that ends the card.
Verification
Self-test is the verification surface (the live sweep cannot run in-container — see the script header's transport note).
Reverse verification, both legs, mutation confirmed on disk. Ablated the branch-activity term (
if (moved.some((m) => m === true)) return null;→if (false) return null;), confirmed the anchor count went 1 → 0 and the injected marker 0 → 1 before reading any result. Predicted direction: red. Observed: red, and exactly the three predicted cases —The mutation script carried a
trap … EXIT INT TERM; the restore leg was verified independently (marker absent, anchor restored, tree clean, 1058 pass).The three existing
summary:pins that broke on the reworded summary line were caught by the suite, not discovered by hand — they are updated, and the new clause is pinned.Gates
Derived with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no hand-written path list — the script takes its own change set from the merge base). 17 families matched; 16 run locally and are green, atb2c8679c:check:cross-package-test-inputs·check:doc-authoring·check:doc-formula-expressions·check:entry-guard·check:parse-guard·check:partof-closing-keyword·check:pm-governed-merges·check:pm-half-states·check:pm-skill-id-lint·check:pm-skill-ratchet·check:pnpm-filter-targets·check:skill-frame-sync·check-ci-filter-parity·check-closing-keyword-parity·check-cross-package-test-inputs·check-nul-bytesThe 17th,
scripts/check-partof-closing-keyword.mjsinvoked directly, refuses to give a verdict withoutPR_BODY/PR_NUMBER— correctly, it is a wiring failure and not a verdict. It was run against this body before pushing, and passes.Ratchet
.claude/skills/pm-dispatch/references/dispatch-runbook.mdwas at its ceiling (243/243). Four lines added — the two this card owes plus the two DEFER lines the seat post carries — paid for by compressing in place, landing back at exactly 243/243, headroom 0. No ceiling raise, so no maintainer ruling is needed.What paid: the maintainer quote duplicated in the emergency-triage section (preserved verbatim in
check-half-states.mjs's H13 docblock — provenance is one line, per the ratchet's own remedy text), one narrative aside about a separate line of work, and re-wrapping. I verified with a word-level diff (git diff --word-diff-regex='[^[:space:]]') that no operative rule was dropped — only provenance prose and whitespace.Runbook lines
Folded DEFER lines: read
rate_limitonce before dispatch, and pnpm 10.31 rejectsinstall --workspace-concurrency(confirmed live here — this worktree installed on pnpm v10.31.0).Out of scope
No changeset: this PR ships no user-visible behaviour — it changes internal PM patrol tooling and a
.claude/instruction face only.skip-changesetapplied.Generated by Claude Code
Generated by Claude Code