fix(scripts): check-half-states probes its transport and reports a prerequisite, not a bare HTTP status (#7412) - #7777
Conversation
…re HTTP status (#7412) `check-half-states.mjs`'s docblock claimed "unauthenticated works at 60 req/h". That is not a fact about this script's environment — three container classes have now been measured and no two agree, so the file stated as universal what is per-container. The docblock now says what live mode actually requires (a route to api.github.com from NODE, whose fetch ignores HTTPS_PROXY and so does not share curl/gh/MCP's path; plus either no token or one that really is a GitHub credential) and records all three measurements, including this change's own: `GITHUB_TOKEN` set to the agent proxy's 14-char `prox…` placeholder, which makes the token fallback strictly worse than sending nothing. Behaviourally, the script now PROBES before it sweeps, in the #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 MET report that names which requirement is unmet, prescribes the one command that satisfies it, and states plainly that nothing was swept. Exit 3 for a classified prerequisite failure, distinct from the pre-existing 2 for one this file cannot name; both non-zero, so no wrapper's behaviour changes. `--probe` answers "can live mode run HERE?" without sweeping. The probe cost one trap to get right, and it is pinned by the self-test: `/rate_limit` is EXEMPT from the limit it reports, so with the quota spent it still answers 200 while every other endpoint 403s. A status-only reading green-lit a sweep that could not make one request — the #4690 shape this file exists to refuse. `parseRemaining` covers the sibling bug: `Number(null)` is 0, which would have turned every 401 into a phantom exhausted quota. Self-test 24 -> 58 cases. No H-predicate is touched (#7553's spelling question is a separate pending ruling), and no transport re-architecture is attempted: which token to send, and whether these scripts should grow an MCP-backed transport, stay the maintainer's call per triage. The script does not drop or substitute the token on its own, so a container where the sweep worked before works identically after. Refs #7341, #7379, #7718
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31510744429 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31512047243 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31512977629 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31513731594 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31514483270 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31515153389 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #7412
File surface:
scripts/pm/check-half-states.mjsonly. No H-predicate is touched.Premise: still valid
Re-verified on
origin/main@76d74ec, immediately before the first edit: the Authparagraph still read "uses GITHUB_TOKEN / GH_TOKEN when present (unauthenticated works
at 60 req/h…)", and the token fallback was unchanged. The file was byte-identical to
main.What the docblock now says
The old paragraph stated as universal what is per-container. The replacement states the
two things live mode actually requires, and records three measured container classes
rather than generalising from one:
Bad credentials; anon 403API rate limit exceededTwo facts from the third row are new and load-bearing:
GITHUB_TOKENbeing set does not make it a GitHub credential. In this container bothGITHUB_TOKENandGH_TOKENare the agent proxy's 14-characterprox…placeholder, sothe token fallback sends a Bearer that GitHub rejects — strictly worse than sending
nothing. This also explains the discrepancy the card recorded but could not resolve
(the script reported 401 while a
curlprobe reported 403): node'sfetchignoresHTTPS_PROXY, so it does not share the pathcurl,ghandmcp__github__*take.curlhere answers 200 both ways because the proxy substitutes a real credential. Acurlpre-flight for this script is therefore meaningless, and the docblock says so.in an agent container it is routinely already spent by neighbours.
What the script now does
Follows the #7718 shape (
scripts/cli-build-prerequisite.mjs+check-i18n-bundles.mjs):pure classifiers the self-test drives with the real observations, wording kept next to the
code that knows what it did not check.
GET /rate_limit(no core quota cost). A second, token-lessprobe fires only when a token was sent and failed; that is what separates "the
credential is bad" from "the host is unreachable", two facts with different remedies that
the card's original measurement could not tell apart. The healthy path stays at one request.
classifyTransportProbe→host-unreachable·bad-credential·bad-credential-anon-reachable·rate-limited·reachable, andnullfor anything itcannot name — deliberately narrow in the same direction as
looksLikeStaleWorkspaceDist,because a confident wrong diagnosis would send a seat hunting for a credential during a
GitHub outage.
PREREQUISITE NOT METreport naming the unmet requirement, the one command thatsatisfies it, and — the load-bearing half — that nothing was swept, so an empty finding
list is never legible as a clean board (check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690). A
sweptcount keeps that paragraph truewhen the in-loop net fires mid-run.
--probeanswers "can live mode run HERE?" without sweeping.0completed sweep ·3classified prerequisite failure ·2unclassifiedcould-not-run (pre-existing). Both failures stay non-zero, so no wrapper's behaviour changes;
the split lets a patrol tell "this container never could" from "something broke".
The trap this cost, now pinned by the self-test
/rate_limitis exempt from the limit it reports: with the quota spent it still answers200carryingx-ratelimit-remaining: 0, while every other endpoint answers 403. The firstdraft read only the status and green-lit a sweep that could not make one request — the exact
"green check that checked nothing" this file exists to refuse. Caught by running the remedy
the report prescribed and watching it 403.
probeIsUsableis that lesson.parseRemainingcovers the sibling bug:Number(null)is0, and a 401 carries norate-limit headers, so the naive read turned every bad credential into a phantom exhausted
quota and misprescribed the fix.
Verification
--self-test, no network). The three containerclasses are pinned as named cases, alongside the two traps and the
null(unclassified)branches.
--probeand the sweep both produce the correct classifiedverdict at exit 3.
fetchfor the paths this container cannot reach: green sweep still emits anidentical finding report at exit 0 (the
sweepIntoextraction is behaviour-preserving);a transient 500 with a healthy transport correctly falls through to the pre-existing
exit 2 instead of blaming the credential; a quota exhausted mid-sweep reports the
nonzero-
sweptwording.eslintcould not be run (nonode_modulesin this container); its only active rule isno-restricted-importsand this file importsnode:processalone.Scope
⛔ Out of scope and not attempted, per triage: widening the H-predicates (#7553's
spelling question is a separate pending ruling), and the transport re-architecture
(MCP-backed sweep vs required-token doctrine) — a maintainer appetite call. The script does
not drop, substitute or re-route the token on its own; a container where the sweep
worked before works identically after.
No changeset:
scripts/pm/**is internal PM tooling and ships in no package. PM to applyskip-changesetif the gate asks for one.The two siblings were assessed in one paragraph each (report-only, no code changed);
both are reported on #7412. Short version:
dispatch-gates.mjshas no network at all andits docblock is already explicit about its heuristic limits, but its two
exit 2throwpaths are the one place the same "state what was not checked" discipline is thinner than
here;
check-skill-line-ratchet.mjsis fully local, single-input, and already fails redon an unreadable input with #4690 cited — no equivalent problem.
Refs #7341, #7379, #7718
Generated by Claude Code