@@ -3006,7 +3006,7 @@ function classifyRepoRead(tok, primary, repo) {
30063006 return {
30073007 kind : 'repo-scope-refused' ,
30083008 headline :
3009- 'the transport authenticates but repo-scoped reads are refused — the sweep cannot list one page ' ,
3009+ 'the transport authenticates but repo-scoped reads are refused — this container cannot make one repo-scoped request ' ,
30103010 detail : [
30113011 `\`GET /rate_limit\` -> ${ describeProbe ( primary ) } .` ,
30123012 `\`GET /repos/${ OWNER_REPO } \` -> HTTP ${ repo . status } ${
@@ -3024,12 +3024,13 @@ function classifyRepoRead(tok, primary, repo) {
30243024 `` ,
30253025 `This is reported instead of a green precisely because the stage-1 reading here` ,
30263026 `is INDISTINGUISHABLE from the healthy Routine runner's. Before this stage` ,
3027- `existed the probe said the prerequisite was met and the sweep then 403'd on its` ,
3028- `first page — the #4690 inversion, inside the mechanism built to prevent it.` ,
3027+ `existed the probe said the prerequisite was met and the first repo-scoped` ,
3028+ `request then 403'd — the #4690 inversion, inside the mechanism built to` ,
3029+ `prevent it.` ,
30293030 ] ,
30303031 fix : [
3031- 'run the sweep from a container whose egress allows repo-scoped reads (CI, or' ,
3032- 'the Routine seat class); in a proxy-mediated seat the board read stays on the' ,
3032+ 'run this from a container whose egress allows repo-scoped reads (CI, or the ' ,
3033+ 'Routine seat class); in a proxy-mediated seat, repo-scoped reads stay on the' ,
30333034 '`mcp__github__*` tools, which take a different path and do work here.' ,
30343035 ] ,
30353036 } ;
@@ -3086,8 +3087,9 @@ export function classifyTransportProbe(obs) {
30863087 `script's transport.` ,
30873088 ] ,
30883089 fix : [
3089- 'run the sweep from a container with direct egress to api.github.com (CI, or' ,
3090- 'the Routine seat class); in an MCP-only seat the board read stays manual.' ,
3090+ 'run this from a container with direct egress to api.github.com (CI, or the' ,
3091+ 'Routine seat class); in an MCP-only seat this stays manual — the' ,
3092+ '`mcp__github__*` tools take a different path and may still work.' ,
30913093 ] ,
30923094 } ;
30933095 }
@@ -3183,8 +3185,9 @@ export function classifyTransportProbe(obs) {
31833185 `\`curl\` and the \`mcp__github__*\` tools take a different path and may still work.` ,
31843186 ] ,
31853187 fix : [
3186- 'run the sweep from a container with direct egress to api.github.com (CI, or' ,
3187- 'the Routine seat class); in an MCP-only seat the board read stays manual.' ,
3188+ 'run this from a container with direct egress to api.github.com (CI, or the' ,
3189+ 'Routine seat class); in an MCP-only seat this stays manual — the' ,
3190+ '`mcp__github__*` tools take a different path and may still work.' ,
31883191 ] ,
31893192 } ;
31903193 }
@@ -5386,6 +5389,22 @@ function selfTest() {
53865389 kind ( { token : 'prox_placeholder' , authed : { status : 403 , rateLimitRemaining : 59 } , anon : { status : 403 , rateLimitRemaining : 59 } } ) ,
53875390 'host-unreachable' ,
53885391 ) ;
5392+ // #10156: the shared classifier is imported by ci-failure.mjs (#9966), a
5393+ // caller that runs no "sweep" and reads no "board" — so `host-unreachable`'s
5394+ // fix text must not name either, in EITHER branch that returns this kind
5395+ // (network error, and the 403-in-both-directions case pinned just above).
5396+ t (
5397+ "…and host-unreachable's fix (403-both-ways branch) names neither the sweep nor the board read" ,
5398+ / t h e s w e e p | t h e b o a r d r e a d / . test (
5399+ classifyTransportProbe ( { token : 'prox_placeholder' , authed : { status : 403 , rateLimitRemaining : 59 } , anon : { status : 403 , rateLimitRemaining : 59 } } ) . fix . join ( ' ' ) ,
5400+ ) ,
5401+ false ,
5402+ ) ;
5403+ t (
5404+ "…and host-unreachable's fix (network-error branch) names neither the sweep nor the board read" ,
5405+ / t h e s w e e p | t h e b o a r d r e a d / . test ( classifyTransportProbe ( { token : '' , anon : { networkError : 'ENOTFOUND' } } ) . fix . join ( ' ' ) ) ,
5406+ false ,
5407+ ) ;
53895408 // Class 2 — triage Routine container: reachable with a real credential.
53905409 t (
53915410 '#7412 class 2 (Routine): authed 200 -> reachable' ,
@@ -5448,6 +5467,22 @@ function selfTest() {
54485467 classifyTransportProbe ( class4 ) . detail . join ( ' ' ) . includes ( 'contradict' ) ,
54495468 true ,
54505469 ) ;
5470+ // #10156: repo-scope-refused is shared with ci-failure.mjs (#9966), a caller
5471+ // that runs no "sweep" and reads no "board" — its headline/detail/fix must
5472+ // describe what the CONTAINER cannot do, not what a caller-specific "sweep"
5473+ // or "board read" cannot do.
5474+ t (
5475+ "…and none of its prose (headline, detail, fix) names the sweep or the board read" ,
5476+ / t h e s w e e p | t h e b o a r d r e a d / . test (
5477+ [ classifyTransportProbe ( class4 ) . headline , ...classifyTransportProbe ( class4 ) . detail , ...classifyTransportProbe ( class4 ) . fix ] . join ( ' ' ) ,
5478+ ) ,
5479+ false ,
5480+ ) ;
5481+ t (
5482+ '…and its headline instead speaks caller-neutrally about the container' ,
5483+ classifyTransportProbe ( class4 ) . headline . includes ( 'this container cannot make one repo-scoped request' ) ,
5484+ true ,
5485+ ) ;
54515486 // Direction B, refused on the card: no vendor string is matched. The fixture
54525487 // carries a status and a header count and NOTHING else — no response body is
54535488 // observed at all — so a body-matching classifier could not have fired here.
0 commit comments