Skip to content

fix(scripts): check-half-states probes its transport and reports a prerequisite, not a bare HTTP status (#7412) - #7777

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-7412-half-states-prereq
Aug 11, 2026
Merged

fix(scripts): check-half-states probes its transport and reports a prerequisite, not a bare HTTP status (#7412)#7777
huangyiirene merged 1 commit into
mainfrom
claude/issue-7412-half-states-prereq

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #7412

File surface: scripts/pm/check-half-states.mjs only. No H-predicate is touched.

Premise: still valid

Re-verified on origin/main @ 76d74ec, immediately before the first edit: the Auth
paragraph 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:

Container class node fetch → api.github.com Live mode
PM seat session (#7412 as filed) 403 with and without token; MCP-only seat cannot run
Triage Routine (#7412 comment, 2026-08-11) 200, real credential, 15000 core quota runs fully
Cloud dev session (this PR's own measurement) token 401 Bad credentials; anon 403 API rate limit exceeded cannot run

Two facts from the third row are new and load-bearing:

  • GITHUB_TOKEN being set does not make it a GitHub credential. In this container both
    GITHUB_TOKEN and GH_TOKEN are the agent proxy's 14-character prox… placeholder, so
    the 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 curl probe reported 403): node's fetch ignores
    HTTPS_PROXY
    , so it does not share the path curl, gh and mcp__github__* take.
    curl here answers 200 both ways because the proxy substitutes a real credential. A
    curl pre-flight for this script is therefore meaningless, and the docblock says so.
  • The anonymous 60 req/h is per egress IP, shared across containers behind one NAT, 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.

  • Probe before sweep — one GET /rate_limit (no core quota cost). A second, token-less
    probe 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.
  • classifyTransportProbehost-unreachable · bad-credential ·
    bad-credential-anon-reachable · rate-limited · reachable, and null for anything it
    cannot 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 MET report naming the unmet requirement, the one command that
    satisfies 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 swept count keeps that paragraph true
    when the in-loop net fires mid-run.
  • --probe answers "can live mode run HERE?" without sweeping.
  • Exit codes: 0 completed sweep · 3 classified prerequisite failure · 2 unclassified
    could-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_limit is exempt from the limit it reports: with the quota spent it still answers
200 carrying x-ratelimit-remaining: 0, while every other endpoint answers 403. The first
draft 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. probeIsUsable is that lesson.

parseRemaining covers the sibling bug: Number(null) is 0, and a 401 carries no
rate-limit headers, so the naive read turned every bad credential into a phantom exhausted
quota and misprescribed the fix.

Verification

  • Self-test 24 → 58 cases, all pass (--self-test, no network). The three container
    classes are pinned as named cases, alongside the two traps and the null (unclassified)
    branches.
  • Live in this container: --probe and the sweep both produce the correct classified
    verdict at exit 3.
  • Stubbed fetch for the paths this container cannot reach: green sweep still emits an
    identical finding report at exit 0 (the sweepInto extraction 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-swept wording.
  • eslint could not be run (no node_modules in this container); its only active rule is
    no-restricted-imports and this file imports node:process alone.

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 apply
skip-changeset if 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.mjs has no network at all and
its docblock is already explicit about its heuristic limits, but its two exit 2 throw
paths are the one place the same "state what was not checked" discipline is thinner than
here; check-skill-line-ratchet.mjs is fully local, single-input, and already fails red
on an unreadable input with #4690 cited — no equivalent problem.

Refs #7341, #7379, #7718


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 2:32pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31510744429 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 22 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31512047243 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 1 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 33 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31512977629 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 2 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 35 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31513731594 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 3 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 42 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31514483270 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 4 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 44 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Aug 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31515153389 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/data/api-methods-batch-conformance.test.ts�[2m > �[22mapiMethods conformance — single-record writes imply batch (#3026)�[2m > �[22mgrants bulk wherever it grants create /
    

历史信号:

  • ⚠️ 本 PR 过去 24h 已在队列失败 5 次(不含本次)。 内容未变而反复失败 ⇒ 高度怀疑 flaky 测试或与同组 PR 的语义冲突,重排不解决。
  • 过去 24h 队列共有 53 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

@huangyiirene
huangyiirene added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 5eb4a7a Aug 11, 2026
23 of 24 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7412-half-states-prereq branch August 11, 2026 18:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant