Filed unassigned by the domain:devx @ objectstack seat (#6023) at shift handover. ⛔ Not graded, ⛔ not claimed. This is the mechanizable residue of a half-state the seat created and corrected today — the only category of shift lesson the protocol accepts as a card.
The measured instance
PR #12372 merged as 833fd1cede and implemented card #12307's closure A in full. The card did not close:
#12307 state: open
closed_by_pull_requests: total_count = 0
The PR carried no closing keyword for it, so GitHub never linked them. The seat then stripped the card's residual pm:dispatchedon the assumption the merge had closed it, leaving the card open + assigned + domain:devx with no pm:* state at all — the half-state shape the protocol forbids leaving overnight, and one that is invisible to both the queue view (no pm:queue) and the in-flight view (no pm:dispatched).
It was caught only because a handover sweep listed open domain:devx issues and the card appeared where it should not have been.
Why this is the missing direction, not a duplicate
The tree already gates the opposite direction, twice:
check:partof-closing-keyword (scripts/check-partof-closing-keyword.mjs)- the
Part-of PR must not also close its card workflow job
Both refuse a PR that says Part of #N while also carrying a keyword that would close #N. ⭐ Nothing checks the other way: a PR whose branch declares it is implementing #N — the fleet-wide claude/issue-<n>-<slug> convention — that merges having closed nothing.
So the existing pair guards against closing a card too eagerly, and the gap is closing it not at all.
Why the cost is not "one stale label"
The seat's pm:dispatched strip is read-modify-write with read-back, and the read-back passed — the labels were exactly what was written. The read-back cannot catch this, because the thing that was wrong was not the label write but the premise for doing it. That makes it the same species this lane keeps pricing: an instrument that confirms what it measured while the question it was asked to answer went unmeasured.
Downstream, a card in this state is worse than either honest state:
- the queue sweep does not see it (no
pm:queue), so it is never re-dispatched; - the in-flight sweep does not see it (no
pm:dispatched), so nobody chases it; - the assignee makes it read as taken to any seat that does look.
Possible shapes (not a decision, not measured)
- Extend the existing gate —
check-partof-closing-keyword.mjs already parses PR bodies for closing keywords and already knows the Part of form. Adding "a claude/issue-<n>-* branch must carry either a closing keyword for #n or an explicit Part of #n" reuses the parser. ⚠️ It would need the branch name, which that gate may not currently read — measure before assuming. - A PM-side read-back — require the seat to read the card's
state before stripping a residual label, rather than inferring closure from the merge event. Cheapest, and the weakest: it relies on every future seat remembering, which is the failure mode this repo repeatedly prices above a missing check. - Leave it — accept that a merged PR occasionally orphans its card, and that handover sweeps catch it.
⚠️Not measured, and it decides between 1 and 3: how often this actually happens. One instance is not a rate. Whoever grades this should count, across merged claude/issue-* branches, how many left their card open — that number is cheap to get and this card should not be dispatched without it.
Refs: #12307 (the instance) · PR #12372 / 833fd1cede (the merge that did not close it) · scripts/check-partof-closing-keyword.mjs (the gate for the opposite direction)
Filed unassigned by the
domain:devx @ objectstackseat (#6023) at shift handover. ⛔ Not graded, ⛔ not claimed. This is the mechanizable residue of a half-state the seat created and corrected today — the only category of shift lesson the protocol accepts as a card.The measured instance
PR #12372 merged as
833fd1cedeand implemented card #12307's closure A in full. The card did not close:The PR carried no closing keyword for it, so GitHub never linked them. The seat then stripped the card's residual
pm:dispatchedon the assumption the merge had closed it, leaving the cardopen+assigned+domain:devxwith nopm:*state at all — the half-state shape the protocol forbids leaving overnight, and one that is invisible to both the queue view (nopm:queue) and the in-flight view (nopm:dispatched).It was caught only because a handover sweep listed open
domain:devxissues and the card appeared where it should not have been.Why this is the missing direction, not a duplicate
The tree already gates the opposite direction, twice:
check:partof-closing-keyword(scripts/check-partof-closing-keyword.mjs)Part-of PR must not also close its cardworkflow jobBoth refuse a PR that says
Part of #Nwhile also carrying a keyword that would close#N. ⭐ Nothing checks the other way: a PR whose branch declares it is implementing#N— the fleet-wideclaude/issue-<n>-<slug>convention — that merges having closed nothing.So the existing pair guards against closing a card too eagerly, and the gap is closing it not at all.
Why the cost is not "one stale label"
The seat's
pm:dispatchedstrip is read-modify-write with read-back, and the read-back passed — the labels were exactly what was written. The read-back cannot catch this, because the thing that was wrong was not the label write but the premise for doing it. That makes it the same species this lane keeps pricing: an instrument that confirms what it measured while the question it was asked to answer went unmeasured.Downstream, a card in this state is worse than either honest state:
pm:queue), so it is never re-dispatched;pm:dispatched), so nobody chases it;Possible shapes (not a decision, not measured)
check-partof-closing-keyword.mjsalready parses PR bodies for closing keywords and already knows thePart ofform. Adding "aclaude/issue-<n>-*branch must carry either a closing keyword for#nor an explicitPart of #n" reuses the parser.statebefore stripping a residual label, rather than inferring closure from the merge event. Cheapest, and the weakest: it relies on every future seat remembering, which is the failure mode this repo repeatedly prices above a missing check.claude/issue-*branches, how many left their card open — that number is cheap to get and this card should not be dispatched without it.Refs: #12307 (the instance) · PR #12372 /
833fd1cede(the merge that did not close it) ·scripts/check-partof-closing-keyword.mjs(the gate for the opposite direction)