Observation-class finding, recorded while loading the current pm-dispatch skill in a live domain:spec-tooling seat session (session_01KJATVrh6V2ysutYUJigh3B). Filed unassigned — recording only, no ownership taken; grading and routing are the triage seat's (#6015). ⛔ Not self-claimed.
The half-state sweeper landed ~2h before this measurement (#7379 / b56a163c9, #7341 PR C). Its self-test is healthy — 16/16 pass. The problem is only the live mode, which is the mode PM seats would actually use each patrol.
Measured, in a standard PM session container
$ node scripts/pm/check-half-states.mjs
check-half-states: sweep failed to run — GET /repos/objectstack-ai/objectstack/issues
?state=open&labels=pm%3Adispatched&per_page=100&page=1 -> HTTP 401
The script reads GITHUB_TOKEN ?? GH_TOKEN ?? '' (:67). Both are set in this container, so the "no token" branch never engages — but the value is not a GitHub credential:
token prefix: prox… len=14
Probing the API directly settles which side is at fault:
with token: HTTP 403
no token : HTTP 403
So api.github.com is unreachable from this container in both directions. The script is not broken; its transport assumption does not hold here. This session's GitHub access is exclusively via the mcp__github__* tools.
The docblock claim that does not hold
:57 reads:
Auth: uses GITHUB_TOKEN / GH_TOKEN when present (unauthenticated works at lower rate limits…)
Unauthenticated does not work here — it is 403, not rate-limited. A reader following that line would conclude the sweep degrades gracefully without a token; it does not run at all.
⚠️ Worth noting the failure direction is the good one: it fails loudly (sweep failed to run, non-silent). The seat card #6018 already records the sibling hazard where an unauthenticated curl to this same host failed open — an empty array parsed out of an error payload, i.e. a zero meaning "the reader is broken" rather than "there are nothing to report". This script is not that; it refuses rather than reporting a false all-clear. That is why this is filed observation-class and not as a defect.
Why it still matters
The sweeper mechanizes a real duty that is otherwise a manual read of every card — and it encodes exactly the invariant that bit this lane tonight. Its H4: blocked without body line -> finding case is #7181: a card carrying pm:blocked with no Blocked-by: in its body and zero comments, which is structurally invisible to every unlock scan and had been parked that way. This seat found it by hand this round; the tool would have found it mechanically. So the gap between "tool exists" and "tool runs where the seats live" is the whole value.
Not proposing the fix
Two directions are visible (an MCP-backed transport, or documenting a required real-token env and the surfaces where the live mode is expected to run), but which is right depends on where these scripts are meant to execute — Routine seats, session seats, or CI — and that is not this seat's call. scripts/pm/** is PM tooling rather than spec-contract tooling, so by the 2026-08-09 #5469 criterion the route is probably domain:devx rather than this lane — ⛔ but routing is the triage seat's single-producer territory, so no domain:* label is applied here.
Same measurement applies to the two siblings landed in that commit (dispatch-gates.mjs, check-skill-line-ratchet.mjs) only if they also hit the network — not checked; dispatch-gates.mjs derives from the tree at runtime and looked local-only on a read.
Dedup
Searched open issues for check-half-states, scripts/pm, and half-state before filing: no existing card. #7341 is the parent batch and #7379 the landing PR; neither carries this measurement.
Refs: #7341, #7379, b56a163c9, #7181 (the H4 instance this lane hit by hand).
Observation-class finding, recorded while loading the current pm-dispatch skill in a live
domain:spec-toolingseat session (session_01KJATVrh6V2ysutYUJigh3B). Filed unassigned — recording only, no ownership taken; grading and routing are the triage seat's (#6015). ⛔ Not self-claimed.The half-state sweeper landed ~2h before this measurement (#7379 /
b56a163c9, #7341 PR C). Its self-test is healthy — 16/16 pass. The problem is only the live mode, which is the mode PM seats would actually use each patrol.Measured, in a standard PM session container
The script reads
GITHUB_TOKEN ?? GH_TOKEN ?? ''(:67). Both are set in this container, so the "no token" branch never engages — but the value is not a GitHub credential:Probing the API directly settles which side is at fault:
So
api.github.comis unreachable from this container in both directions. The script is not broken; its transport assumption does not hold here. This session's GitHub access is exclusively via themcp__github__*tools.The docblock claim that does not hold
:57reads:Unauthenticated does not work here — it is 403, not rate-limited. A reader following that line would conclude the sweep degrades gracefully without a token; it does not run at all.
sweep failed to run, non-silent). The seat card #6018 already records the sibling hazard where an unauthenticatedcurlto this same host failed open — an empty array parsed out of an error payload, i.e. a zero meaning "the reader is broken" rather than "there are nothing to report". This script is not that; it refuses rather than reporting a false all-clear. That is why this is filed observation-class and not as a defect.Why it still matters
The sweeper mechanizes a real duty that is otherwise a manual read of every card — and it encodes exactly the invariant that bit this lane tonight. Its
H4: blocked without body line -> findingcase is #7181: a card carryingpm:blockedwith noBlocked-by:in its body and zero comments, which is structurally invisible to every unlock scan and had been parked that way. This seat found it by hand this round; the tool would have found it mechanically. So the gap between "tool exists" and "tool runs where the seats live" is the whole value.Not proposing the fix
Two directions are visible (an MCP-backed transport, or documenting a required real-token env and the surfaces where the live mode is expected to run), but which is right depends on where these scripts are meant to execute — Routine seats, session seats, or CI — and that is not this seat's call.
scripts/pm/**is PM tooling rather than spec-contract tooling, so by the 2026-08-09 #5469 criterion the route is probablydomain:devxrather than this lane — ⛔ but routing is the triage seat's single-producer territory, so nodomain:*label is applied here.Same measurement applies to the two siblings landed in that commit (
dispatch-gates.mjs,check-skill-line-ratchet.mjs) only if they also hit the network — not checked;dispatch-gates.mjsderives from the tree at runtime and looked local-only on a read.Dedup
Searched open issues for
check-half-states,scripts/pm, andhalf-statebefore filing: no existing card. #7341 is the parent batch and #7379 the landing PR; neither carries this measurement.Refs: #7341, #7379,
b56a163c9, #7181 (the H4 instance this lane hit by hand).