Blocked-by: #9946
Filed unassigned by the #9946 dev seat (session ae0d8bd1-5667-5f58-8ff8-fa22ec154325), out of that card's scope: #9946 is scoped to scripts/pm/check-half-states.mjs, and this is the identical defect one file over. Duplicate search first: keyword sweep over open issues for the ci-failure / probe / repo-scoped-403 shapes — no hit other than #9946 itself.
The shape
scripts/pm/ci-failure.mjs deliberately imports classifyTransportProbe rather than copying it ("one instrument", per its own header). It gathers only the account-scoped observation:
async function probeTransport() {
const authed = await probeRateLimit(TOKEN);
...
return { verdict: classifyTransportProbe({ token: TOKEN, authed, anon }), authed, anon };
}
Its walk then reads repo-scoped paths through rest() — /repos/{owner}/{repo}/commits/{sha}/check-runs, job logs, and so on. In a proxy-mediated container those are exactly the paths the egress proxy refuses, while /rate_limit reaches GitHub and answers 200 with a real quota.
Measured, 2026-08-19, in this container
Reproducing ci-failure.mjs's probe call exactly, then the first read its walk makes:
ci-failure-shaped probe verdict: reachable
the walk it green-lights: GET /repos/.../check-runs -> 403
So the same inversion #9946 records: the probe reports the prerequisite met, and the thing it green-lights cannot make one request. Here the consequence is that a PREREQUISITE NOT MET (exit 3, "this classifies the ENVIRONMENT, not the tree") degrades into an UNDETERMINED or a raw HTTP number, which is the reading its own header says exit 3 exists to prevent.
Why this is a separate card
The remedy is available once #9946 lands: that PR adds an optional repo-scoped observation to classifyTransportProbe plus a repo-scope-refused verdict, chosen so the second observation is opt-in precisely because this file is the other importer and had to keep classifying identically. Adopting it here is a few lines — gather GET /repos/{owner}/{repo} when the account-scoped verdict reads reachable, and pass it through — but it is a change to a different file with its own self-test, and #9946 was dispatched single-file on a hot path.
Worth deciding at triage rather than assuming: whether the repo-scoped stage belongs in each caller (as #9946 leaves it, gathering policy per script) or whether the two scripts should share one probeTransport now that both need the same two stages. The second is tidier and is a wider change than either card has scoped.
Generated by Claude Code
Blocked-by: #9946
Filed unassigned by the #9946 dev seat (session
ae0d8bd1-5667-5f58-8ff8-fa22ec154325), out of that card's scope: #9946 is scoped toscripts/pm/check-half-states.mjs, and this is the identical defect one file over. Duplicate search first: keyword sweep over open issues for the ci-failure / probe / repo-scoped-403 shapes — no hit other than #9946 itself.The shape
scripts/pm/ci-failure.mjsdeliberately importsclassifyTransportProberather than copying it ("one instrument", per its own header). It gathers only the account-scoped observation:Its walk then reads repo-scoped paths through
rest()—/repos/{owner}/{repo}/commits/{sha}/check-runs, job logs, and so on. In a proxy-mediated container those are exactly the paths the egress proxy refuses, while/rate_limitreaches GitHub and answers 200 with a real quota.Measured, 2026-08-19, in this container
Reproducing
ci-failure.mjs's probe call exactly, then the first read its walk makes:So the same inversion #9946 records: the probe reports the prerequisite met, and the thing it green-lights cannot make one request. Here the consequence is that a PREREQUISITE NOT MET (exit 3, "this classifies the ENVIRONMENT, not the tree") degrades into an UNDETERMINED or a raw HTTP number, which is the reading its own header says exit 3 exists to prevent.
Why this is a separate card
The remedy is available once #9946 lands: that PR adds an optional repo-scoped observation to
classifyTransportProbeplus arepo-scope-refusedverdict, chosen so the second observation is opt-in precisely because this file is the other importer and had to keep classifying identically. Adopting it here is a few lines — gatherGET /repos/{owner}/{repo}when the account-scoped verdict readsreachable, and pass it through — but it is a change to a different file with its own self-test, and #9946 was dispatched single-file on a hot path.Worth deciding at triage rather than assuming: whether the repo-scoped stage belongs in each caller (as #9946 leaves it, gathering policy per script) or whether the two scripts should share one
probeTransportnow that both need the same two stages. The second is tidier and is a wider change than either card has scoped.Generated by Claude Code