Repository navigation
[finding] scripts/pm/check-half-states.mjs live sweep cannot run in a PM session container — direct api.github.com is 403 with or without the token, and the docblock's "unauthenticated works" is false here #7412
Description
Activity
Triage:
finding+domain:devx(held, not queued).- Classification: observation-class, exactly as self-filed — the live sweep fails loudly (fail-closed) and its self-test is 16/16 green; no patrol silently loses anything today. The docblock mismatch is real:
origin/main@b948a41scripts/pm/check-half-states.mjsstill reads "unauthenticated works at 60 req/h" (docblock) while PM session containers measure 403 both with and without the proxy token. - Routing:
scripts/pm/**is PM/dev tooling, not spec-contract tooling ⇒domain:devxper the 2026-08-09 域标签词表有两个未入表条目:domain:spec-tooling(在用,文件面与 devx 完全重叠)与domain:ui(一例,与repo:objectui重复) #5469 criterion (the filer's own read, confirmed at triage). - Dedup: no other open card covers the PM-container transport assumption of these scripts; feat(scripts): pm patrol tooling — half-state sweeper, dispatch-gates deriver, SKILL line ratchet (#7341 PR C) #7379 (the landing card) is closed. Clean.
- Promotion triggers: a PM seat needing the live sweep in-session (the duty it mechanizes is real — its H4 case is Three more dist-reading spec gates carry the same unenforced freshness precondition as gen:api-surface #7181's shape), or a maintainer call on where
scripts/pm/**live modes are meant to run (MCP-backed transport vs a documented real-token env).
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Classification: observation-class, exactly as self-filed — the live sweep fails loudly (fail-closed) and its self-test is 16/16 green; no patrol silently loses anything today. The docblock mismatch is real:
Findings round — held (
finding+domain:devxkept), premise re-verified.- Re-checked @
origin/main36237a1:scripts/pm/check-half-states.mjs:57still claims "unauthenticated works at lower rate limits", while this session's container measures the opposite transport shape the card recorded (this round's own reads went through the proxy credential — direct-API reachability varies by container class, which is exactly the card's point: the docblock over-promises). - Held: the live sweep fails loudly (fail-closed) and its self-test is green, so no patrol silently loses anything; the fix is a docblock/transport-assumption edit for whoever next touches the PM tooling.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Re-checked @
Findings cadence (triage seat): HOLD — re-verified on
origin/main@7e93c89d: the docblock still claims "unauthenticated works" (scripts/pm/check-half-states.mjs:57) and the token fallback chain is unchanged (:67). One counter-observation added as evidence: in this Routine's container directapi.github.comIS reachable and the proxy-injectedGITHUB_TOKENauthenticates as a real credential (rate_limit answers 200 with the 15000-core quota) — so the transport failure the card measures is container-class-specific, not fleet-wide; a fix should probe rather than assume either way. Neighbour #7488 (H2 regex vs claim-blockquote shape) was promoted topm:queuethis round — same file, different defect.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings cadence (triage seat): promoted —
finding→pm:queue(domain:devxkept), with a narrowed dispatch scope.Premise re-verified on
origin/main@2c1988c: the docblock still claims "unauthenticated works at 60 req/h" (scripts/pm/check-half-states.mjs, Auth paragraph) — false in the containers PM seats actually run in, whereapi.github.comanswers 403 both with and without the proxy token (the card's measurement; this Routine seat's own container shows the same shape — GitHub access is MCP-only here, and #7412's sibling measurement is already cited in the SKILL's operational notes).Scope of the promoted card (deliberately narrow): correct the Auth docblock to state the real transport requirement — live mode needs direct
api.github.comreachability plus a real GitHub credential; MCP-only seat containers cannot run it and get a loud non-silent failure (the good direction). Document which surfaces the live mode is expected to run on as far as that is known (CI or a network-open container), and apply the same one-paragraph check to thedispatch-gates.mjs/check-skill-line-ratchet.mjssiblings (the card left them unmeasured;dispatch-gates.mjslooked local-only). ⛔ Out of scope: the transport re-architecture (MCP-backed sweep vs required-token doctrine) — that depends on where these scripts are meant to execute, which remains a maintainer/appetite call; the docblock fix must not pre-empt it, only stop lying about the current one.Why promote instead of a 4th hold: the premise is environmental and permanently frozen from this seat's viewpoint — re-verifying it each cadence buys nothing, while the stale docblock actively misleads the next seat that loads the tool. The narrow fix is minutes-sized and decision-free.
Not
target:v17(internal PM tooling).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsClaim: PM loop round 4 (devx seat, 2026-08-11 term) — wave 4 unit C
Session:session_01GZKbx4xyF7U5WXj6ch49BM
Branch:claude/issue-7412-half-states-prereq
Worktree: cloud session, own clone
Domain:domain:devx
File surface:scripts/pm/check-half-states.mjsonly (docblock honesty + an explicit prerequisite-not-met failure mode for the in-container 403, in the #7718 pattern — state what was NOT checked instead of implying the sweep ran; ⛔ do not widen the H-predicates, #7553's spelling question is a separate pending ruling)
Container & model: M,mode:cloud,model: opus
Serial constraints cleared: no other claim or open PR touches this file (PR #7551 merged; #7553 isdomain:skillsqueue, not in flight — checked at claim time).
Generated by Claude Code
huangyiirene commented
on Aug 11, 2026 CollaboratorMore actionsos-dev report — #7412 (wave 4 unit C,
domain:devx)- premise_still_valid: true. Re-verified on
origin/main@76d74ecimmediately before
the first edit:scripts/pm/check-half-states.mjswas byte-identical tomain, and the Auth
paragraph still read "uses GITHUB_TOKEN / GH_TOKEN when present (unauthenticated works at
60 req/h…)" with theTOKENfallback unchanged. - PR: fix(scripts):
check-half-statesprobes its transport and reports a prerequisite, not a bare HTTP status (#7412) #7777 (draft, basemain, body carriesFixes #7412). Branch
claude/issue-7412-half-states-prereq. Not merged, no auto-merge, no merge-queue action —
CI convergence and ready-flip are PM's. - File surface:
scripts/pm/check-half-states.mjsonly — 1 file, +560/−16. No changeset
(internal PM tooling, ships in no package); PM to applyskip-changesetif the gate asks.
Landing points
- Docblock honesty. The old Auth paragraph stated as universal what is per-container. It is
replaced by a Transport prerequisite — MEASURED per run, never assumed section naming the two
real requirements and recording all three measured container classes, plus an Exit codes section. - prerequisite-not-met mode, in the fix(scripts): report a stale workspace dist as a prerequisite, not a bundle problem (#7681) #7718 shape: pure classifiers the self-test drives with the
real observations, wording kept next to the code that knows what it did not check, and a
PREREQUISITE NOT METreport that names the unmet requirement, prescribes one command, and states
that nothing was swept (asweptcount keeps that true when the in-loop net fires mid-run).
Exit 3 classified · 2 unclassified (pre-existing) · 0 completed sweep. Both failures
stay non-zero, so no wrapper's behaviour changes. - Probe, don't assume. One
GET /rate_limit(no core quota) before sweeping; a second
token-less probe fires only when a token was sent and failed.--probeanswers "can live mode
run HERE?" without sweeping.
Probe logic, and what measuring it changed
Three container classes now on record — the card's, triage's, and this session's:
Class node fetch → api.github.com Live mode PM seat (#7412 as filed) 403 both ways, MCP-only cannot run Triage Routine (2026-08-11) 200, real credential, 15000 quota runs fully Cloud dev (this session) token → 401 Bad credentials; anon → 403API rate limit exceededcannot run Two findings from the third row resolve things the card left open:
- The card's own 401-vs-403 discrepancy is explained. Node's
fetchignoresHTTPS_PROXY,
so it does not share the pathcurl/gh/mcp__github__*take. Herecurlanswers 200 both
with and without the token (the proxy substitutes a real credential) while node's fetch 401s.
So acurlpre-flight for this script is meaningless — now stated in the docblock. GITHUB_TOKENbeing set does not make it a GitHub credential. In this container both vars are
the proxy's 14-charprox…placeholder, so the token fallback is strictly worse than sending
nothing. Also: the anonymous 60 req/h is counted per egress IP, shared across containers, so
it is routinely already spent — which makes the old "unauthenticated works" claim false twice over.- The trap the probe cost, now pinned by the self-test:
/rate_limitis exempt from the limit
it reports — with the quota spent it still answers200carryingx-ratelimit-remaining: 0while
every other endpoint 403s. My first draft read only the status and green-lit a sweep that could not
make one request: the exact check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 shape this file exists to refuse. Caught by running the remedy the
report prescribed and watching it fail. Sibling bug fixed alongside:Number(null)is0, and a 401
carries no rate-limit headers, so the naive header read turned every bad credential into a phantom
exhausted quota and misprescribed the fix.
Verification. Self-test 24 → 58 cases, all pass. Live in this container both
--probeand the
sweep give the correct classified verdict at exit 3. Paths this container cannot reach were exercised
with a stubbedfetch: green sweep emits an identical finding report at exit 0 (thesweepInto
extraction is behaviour-preserving), a transient 500 on a healthy transport correctly falls through to
exit 2 rather than blaming the credential, and a mid-sweep quota exhaustion produces the nonzero-swept
wording.eslintcould not run (nonode_moduleshere); its only active rule isno-restricted-imports
and this file importsnode:processalone.Sibling one-paragraph assessments — report-only, no code changed
scripts/pm/dispatch-gates.mjs— no equivalent problem; one milder, adjacent gap. It makes zero
network calls: it derives everything at runtime from.github/workflows/*.yml,package.jsonand the
check scripts' own sources, and resolvesROOTfromimport.meta.urlrather thancwd, so it has no
transport prerequisite and no cwd prerequisite to lie about. Its docblock is already unusually honest
about its limits — it says the matching is "honest but heuristic", and that a check with no discoverable
hints is "NOT known to be irrelevant". It also already cites #4690 for its failure path. The one place
the discipline is thinner than what #7777 now does: its two guard throws (no workflow files found,
no check:* invocations found) and the catch-all surface asdispatch-gates: derivation failed — <msg>
and exit 2, without saying what was not derived. That matters slightly more here than it looks, because
an empty match is a legitimate normal result for this tool, so a failed derivation and "no gate
watches this path" can read alike to a hurried PM. Loud, non-silent, and exit 2 — so this is a wording
improvement, not a defect, and it did not meet the bar for widening this card's file surface.scripts/pm/check-skill-line-ratchet.mjs— no problem of this class at all. Fully local, single
input, no network, no token, no environment assumption: it reads one file through a URL resolved from
import.meta.url(cwd-independent) and its docblock makes no environmental claim that could go stale.
It already handles the #4690 case explicitly and correctly in both directions — an unreadable file
exits 1 with "red, not a skip", and a zero-line read is a distinct red verdict rather than a pass, both
pinned by self-test cases. Nothing to fix and nothing to document.Out of scope — observed, deliberately not touched
- ⛔ H-predicates not widened — The H2 half-state predicate is blind to two MORE claim spellings in live use — measured, 9 of 22 pm-tracked cards are false positives after #7488's fix #7553's claim-spelling question is a separate pending ruling.
- ⛔ No transport re-architecture (MCP-backed sweep vs required-token doctrine) — maintainer
appetite call per triage. The script does not drop, substitute or re-route the token on its own;
a container where the sweep worked before works identically after. It only reports the observation
and prescribes the re-run when anonymous access is genuinely usable. - Minor, unfixed, not mine:
dispatch-gates.mjs:57usesnew URL('../..', import.meta.url).pathname,
which yields a broken path on Windows (/C:/…). Every other repo script I read uses the same idiom, so
this is a repo-wide convention question rather than a bug in that file — noting it, not filing it. - Base-red
check:platform-checklist/coverage.json · qa: UNCLASSIFIED(finding:check:platform-checklistis red onmain— the newqaliveness ledger is neither mapped nor waived in coverage.json #7347) acknowledged as
pre-existing and untouched.
Generated by Claude Code
- premise_still_valid: true. Re-verified on
- added a commit that references this issue
on Aug 17, 2026
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).