Skip to content

Commit d4cb05c

Browse files
os-elon-muskclaude
andauthored
fix(pm): the proxy re-exec guard is the CALLER’s name, and it speaks when it suppresses (#18980)
Fixes #18939 Clause-②: no `skip-changeset`: the whole diff is `scripts/pm/**` (PM loop tooling). Nothing in any package's `files[]` moves, so nothing publishes. ## What was wrong `proxyRearmPlan` (`scripts/pm/check-half-states.mjs`) read **one hard-coded** guard variable — `OS_HALF_STATES_PROXY_REARMED`, this file's own — while every consumer sets its **own** name on the child it re-execs. So any sibling instrument's guard, once inherited, answered "already re-armed" for a tool that had **never** re-armed. The suppressed run then took the bypassed route and answered **401 Bad credentials** on every endpoint, `/rate_limit` included — a self-consistent story about a dead credential, and the recorded cost of reading it as one is about an hour spent treating a live channel as dead. Two separate halves, and the second one is the expensive one: 1. the guard **name** was shared where it should have been per-caller; 2. the suppression was **silent** — `rearm: false, hint: false` prints nothing at any consumer, so the run log carried no line pointing at the cause. ## The fix ```js proxyRearmPlan({ env, execArgv, flagSupported, guard = PROXY_REARM_GUARD }) // -> { rearm, hint, flag?, guarded?, reason } ``` - `guard` is a parameter defaulting to this file's own name. A caller passes the variable it actually sets, so only its own guard can stop its own re-exec. - The guard branch returns `hint: true` and `guarded: guard`, and its `reason` names the variable, the proxy and the 401 it would otherwise be mistaken for. `hint` is the one branch **every** consumer already prints, so the silence is gone repo-wide — including for the nine consumers this PR does not edit. - That branch can only fire on the anomaly: a real re-exec's child carries `--use-env-proxy` in its `execArgv` and is answered by the `routed` branch above it, never by this one. - `post-stamped.mjs`, `label-write.mjs` and `board-snapshot.mjs` each pass `guard: PROXY_REARM_GUARD` and drop the own-guard `if` that sat **after** `if (!plan.rearm) return null;` — it was never reached for this defect and is now the plan's own branch. Their child env is unchanged: each still sets its own name. ⛔ No behaviour change where no proxy is configured. On a GitHub Actions runner the `no HTTPS_PROXY` branch returns first, so no child is spawned and no extra word is printed; re-pinned in both directions. ## A/B — measured, same box, same minute, `--dry-run`, no write Exit codes captured redirect-then-`$?`, never across a pipe. No token is printed by any of these. **Before** (`26c73fb4e`), the card's own repro, reproduced here: ``` $ node scripts/pm/label-write.mjs --repo objectstack-ai/objectstack --issue 18932 --add finding --dry-run ℹ️ re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) and node's fetch does not read it. […] ① 取现集 — #18932 (open) carries 3 label(s) … CONTROL_EXIT=0 $ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/label-write.mjs (same args) […] ① read — COULD NOT READ: GET /repos//issues/18932 -> HTTP 401 (Bad credentials) PREREQUISITE NOT MET … ⛔ NOT MEASURED PROBE_EXIT=3 ← and not one line said why ``` **After** (`e424b1cf9`), the same two commands plus the own-guard leg: ``` $ node scripts/pm/label-write.mjs (same args) ℹ️ re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) … AFTER_CONTROL_EXIT=0 $ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/label-write.mjs (same args) ℹ️ re-exec with --use-env-proxy: HTTPS_PROXY is set (http://127.0.0.1:37101) … […] ① 取现集 — #18932 (open) carries 3 label(s) … AFTER_SHARED_EXIT=0 ← the sibling's guard no longer suppresses this tool $ OS_LABEL_WRITE_PROXY_REARMED=1 node scripts/pm/label-write.mjs (same args) ℹ️ OS_LABEL_WRITE_PROXY_REARMED=1 is set and this process is NOT routed through http://127.0.0.1:37101, so the one re-exec this run allows was already spent and every request below bypasses the proxy — if they answer 401 Bad credentials, that inherited guard is why, and unsetting OS_LABEL_WRITE_PROXY_REARMED is the fix rather than a new token. A refusal below may be about the route, not this container. […] ① read — COULD NOT READ: … -> HTTP 401 (Bad credentials) AFTER_OWN_EXIT=3 ← still exit 3, but the cause is now the FIRST line on stderr ``` And the half that needed no edit — an **unedited** consumer, `sweep-stale-finding.mjs`: ``` $ OS_HALF_STATES_PROXY_REARMED=1 node scripts/pm/sweep-stale-finding.mjs --dry-run ℹ️ OS_HALF_STATES_PROXY_REARMED=1 is set and this process is NOT routed through … 401 … sweep-stale-finding: PREREQUISITE NOT MET — GET /repos/…/issues?… -> HTTP 401 EXIT=3 ``` Its re-exec is still suppressed (it passes no `guard` — see the table), but it is no longer suppressed **in silence**, which is what the triage asked the deliverable to contain. ⛔ Not measured, and not claimed: whether a real seat's process actually inherits one of these variables from a sibling. The card flagged that as unmeasured and it stays unmeasured — every leg above sets the variable by hand. ## The 13 consumers, read at `26c73fb4e` `git grep -l proxyRearmPlan scripts` lists 14 files; one of them defines the plan, so 13 consume it. Only the three in the first row are edited. | consumer | shape | after this PR | |---|---|---| | `pm/post-stamped.mjs`, `pm/label-write.mjs`, `pm/board-snapshot.mjs` | own guard name, `process.env` handed to the plan raw | **edited** — each passes its own `guard`; cross-suppression gone, and a suppressing guard speaks | | `check-single-claim-paths.mjs` (`proxyPlanEnv()`), `pm/check-expected-skips.mjs`, `pm/check-prior-rulings.mjs` | map their own name **onto** the shared default, then call | unchanged and still correct — the default is untouched, pinned in this PR from the plan's side; they gain the loud line for free | | `pm/changeset-deadline-census.mjs`, `pm/check-clause2-carriers.mjs`, `pm/sweep-closed-cards.mjs`, `pm/sweep-stale-finding.mjs` | own guard name, `process.env` raw — **the same live defect** | **not edited** (out of this card's ruled surface): still cross-suppressible, but no longer silently. Filed as a finding | | `check-issue-citations.mjs` | calls the plan for refusal **text** only; never re-execs | unaffected | | `pm/check-governed-merges.mjs` (declares a second plan of the same name), `pm/ci-failure.mjs` (imports it) | a separate implementation reading `OS_GOVERNED_MERGES_PROXY_REARMED`, shared across those two files | untouched by this PR; the same one-name-two-scripts shape one layer over. Filed as a finding | ## Self-test counts, before / after | file | cases | batteries | floor | |---|---|---|---| | `pm/check-half-states.mjs` | 4963 → **4975** | 6 → 6 (new cases are unbatteried `t()`) | 6 → 6, every battery pin holds | | `pm/post-stamped.mjs` | 349 → **360** | 13 → **14** | 13 → 13 (held, see below) | | `pm/label-write.mjs` | 64 → **75** | 8 → **9** | 8 → **9** | | `pm/board-snapshot.mjs` | 145 → **156** | 11 → **12** | 10 → **12** (it had drifted below its own roster) | `post-stamped`'s `SELF_TEST_BATTERY_FLOOR` line sits inside PR #18934's open hunk range, so it is deliberately **not** touched: the roster rises to 14 while the pinned floor holds at 13. Both sides stay green whichever lands first, and #18934's own bump to 14 still applies. Every new battery is declared, registered and floored. Each of the three callers gains the same battery — *the re-exec guard: the name this tool sets, and the patrol name that must not silence it* — pinning: this tool's guard is not the patrol's; the patrol literal pinned there **is** the plan's default (so renaming the shared constant reds the pin instead of leaving a phantom); a proxied run with no guard re-execs; its own guard suppresses; the patrol's does not; the suppression prints a line naming the variable and the 401; the runner leg is unchanged; and two structural properties — the dispatch really hands the plan this file's guard, and the plan is imported rather than restated. ## Reader test A seat whose parent process leaked `OS_HALF_STATES_PROXY_REARMED=1` runs `label-write` and reads its **banner**, not a 401 story. If the leaked variable is instead that tool's *own* guard, the seat still gets exit 3 — correctly, the re-exec really was spent — but the first line on stderr names the variable and says unsetting it is the fix, so nobody goes looking for a new token. ## Acceptance notes Noted while reading the surface, ⛔ not fixed here (outside this card's ruled surface), reported to the seat for filing: - Four more consumers carry the **identical** defect and were not in the card's count: `pm/changeset-deadline-census.mjs`, `pm/check-clause2-carriers.mjs`, `pm/sweep-closed-cards.mjs`, `pm/sweep-stale-finding.mjs`. Each declares its own guard and hands `process.env` to the plan raw. One line each would fix them. The card explicitly read **0** own-guard names in `check-clause2-carriers.mjs`; the name is there at `:816`. - `pm/check-governed-merges.mjs` declares a **second** `proxyRearmPlan` rather than importing the patrol's, and shares its single guard name with `pm/ci-failure.mjs` — the same cross-suppression shape between those two files, in a plan this PR does not touch. - `noted, not filed:` the card's 「装饰性」 reading of the three own-guard constants is half right — each **is** read, by its own file and by nothing else. No carrier: this is a correction to a card body, not a defect in the tree. --- _Generated by [Claude Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7d70612 commit d4cb05c

4 files changed

Lines changed: 172 additions & 10 deletions

File tree

‎scripts/pm/board-snapshot.mjs‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,17 +1766,24 @@ async function main(argv) {
17661766
}
17671767

17681768
function rearmThroughProxy(args) {
1769+
// `guard` is THIS tool's own variable (#18939). Without it the plan read the
1770+
// patrol's shared name straight out of `process.env`, so a sibling
1771+
// instrument's inherited guard answered "already re-armed" here — silently —
1772+
// and the un-re-armed run then bypassed the proxy and answered 401 Bad
1773+
// credentials, a false story about the credential. The own-guard `if` that
1774+
// used to sit below was never reached for that case; the plan's own branch
1775+
// now covers it, and PRINTS the variable through `plan.hint`.
17691776
const plan = proxyRearmPlan({
17701777
env: process.env,
17711778
execArgv: process.execArgv,
17721779
flagSupported: process.allowedNodeEnvironmentFlags.has(PROXY_FLAG),
1780+
guard: PROXY_REARM_GUARD,
17731781
});
17741782
if (plan.hint) {
17751783
console.error(`ℹ️ ${plan.reason}. A refusal below may be about the route, not this container.`);
17761784
return null;
17771785
}
17781786
if (!plan.rearm) return null;
1779-
if (process.env[PROXY_REARM_GUARD] === '1') return null;
17801787
console.error(`ℹ️ re-exec with ${plan.flag}: ${plan.reason}.`);
17811788
const quiet = process.allowedNodeEnvironmentFlags.has('--disable-warning') ? ['--disable-warning=UNDICI-EHPA'] : [];
17821789
const child = spawnSync(process.execPath, [plan.flag, ...quiet, SELF_PATH, ...args], {
@@ -1888,6 +1895,7 @@ function measuredHistoryManifest(over = {}) {
18881895
}
18891896

18901897
const SELF_TEST_BATTERIES = Object.freeze({
1898+
'the re-exec guard: the name this tool sets, and the patrol name that must not silence it': 11,
18911899
'the record shapes: fixed key order, declared absence': 9,
18921900
'the page walk: only a short page ends it': 10,
18931901
'the walk plan: the open board first, then the closed history, then incremental': 15,
@@ -1900,7 +1908,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
19001908
'the delta walk, driven: the archive catches up with the live board': 19,
19011909
'the freshness row: a stale archive is never a green run': 18,
19021910
});
1903-
const SELF_TEST_BATTERY_FLOOR = 10;
1911+
const SELF_TEST_BATTERY_FLOOR = 12;
19041912
const UNATTRIBUTED_BATTERY = '(unattributed)';
19051913

19061914
let selfTestReachedVerdict = false;
@@ -1945,6 +1953,31 @@ export async function selfTest() {
19451953
});
19461954

19471955
// -- the record shapes -----------------------------------------------------
1956+
// ── the re-exec guard (#18939) ────────────────────────────────────────────
1957+
// The plan reads the guard name THIS file sets, never a shared one. A sibling
1958+
// instrument's inherited guard used to answer 'already re-armed' here, and the
1959+
// un-re-armed run then bypassed the proxy and answered 401 Bad credentials —
1960+
// a false story about the credential, printed nowhere at all.
1961+
battery('the re-exec guard: the name this tool sets, and the patrol name that must not silence it');
1962+
{
1963+
const PATROL_GUARD = 'OS_HALF_STATES_PROXY_REARMED';
1964+
const proxied = { HTTPS_PROXY: 'http://127.0.0.1:40309' };
1965+
const rearm = (env) => proxyRearmPlan({ env, guard: PROXY_REARM_GUARD, flagSupported: true });
1966+
const own = { ...proxied, [PROXY_REARM_GUARD]: '1' };
1967+
const ownSource = readFileSync(SELF_PATH, 'utf8');
1968+
t('this tool\'s guard is its own name, never the patrol\'s', PROXY_REARM_GUARD !== PATROL_GUARD);
1969+
t('…and the patrol name pinned here IS the plan\'s default, so a rename reds this battery', proxyRearmPlan({ env: { ...proxied, [PATROL_GUARD]: '1' } }).guarded === PATROL_GUARD);
1970+
t('a proxied run with no guard set re-execs', rearm(proxied).rearm === true);
1971+
t('…this tool\'s OWN guard is what stops the loop', rearm(own).rearm === false);
1972+
t('…while the patrol\'s inherited guard does NOT suppress it', rearm({ ...proxied, [PATROL_GUARD]: '1' }).rearm === true);
1973+
t('a suppressed run SPEAKS — silence is the whole cost of this chain', rearm(own).hint === true);
1974+
t('…naming the variable a reader has to unset', rearm(own).reason.includes(PROXY_REARM_GUARD));
1975+
t('…and naming the 401 the silence would otherwise be read as', rearm(own).reason.includes('401 Bad credentials'));
1976+
t('the Actions-runner leg is unchanged: no proxy, no re-exec, no extra line', rearm({ [PROXY_REARM_GUARD]: '1' }).rearm === false && rearm({ [PROXY_REARM_GUARD]: '1' }).hint === false);
1977+
t('structural: the dispatch really hands the plan THIS file\'s guard', /\n\s+guard: PROXY_REARM_GUARD,\n/.test(ownSource));
1978+
t('structural: the plan is imported, not restated here', /\bproxyRearmPlan\b/.test(ownSource) && !/function\s+proxyRearmPlan\b/.test(ownSource));
1979+
}
1980+
19481981
battery('the record shapes: fixed key order, declared absence');
19491982
const record = issueRecord(RAW());
19501983
t('the key order is the one this file writes, not the order GitHub answered in',

‎scripts/pm/check-half-states.mjs‎

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19653,6 +19653,12 @@ export const EXIT_PREREQUISITE_NOT_MET = 3;
1965319653
* identically — both are read at process START, which is the whole reason this
1965419654
* is a re-exec rather than an assignment (header, "Routing node through the
1965519655
* session proxy").
19656+
*
19657+
* `PROXY_REARM_GUARD` is THIS file's OWN guard and the plan's DEFAULT — it is
19658+
* not a name every instrument shares (#18939). A consumer that sets its own
19659+
* variable on the child it spawns passes that name to `proxyRearmPlan` as
19660+
* `guard`; the consumers that instead map their own name ONTO this one before
19661+
* calling keep working through the default, unchanged.
1965619662
*/
1965719663
export const PROXY_FLAG = '--use-env-proxy';
1965819664
export const PROXY_REARM_GUARD = 'OS_HALF_STATES_PROXY_REARMED';
@@ -19684,16 +19690,46 @@ export function proxyRoute({ env = {}, execArgv = [] } = {}) {
1968419690
* VERBATIM into a sibling repo (#11217): a GitHub Actions runner carries no
1968519691
* proxy env, so it never re-execs and behaves exactly as it did before.
1968619692
*
19687-
* @param {{ env?: Record<string,string|undefined>, execArgv?: string[], flagSupported?: boolean }} [ctx]
19688-
* @returns {{ rearm: boolean, hint: boolean, flag?: string, reason: string }}
19689-
*/
19690-
export function proxyRearmPlan({ env = {}, execArgv = [], flagSupported = true } = {}) {
19693+
* ## The guard is a NAME, and it belongs to the CALLER (#18939)
19694+
*
19695+
* Every consumer sets its OWN variable on the child it re-execs, but this plan
19696+
* read ONE hard-coded name — this file's. So ANY sibling instrument's guard,
19697+
* once inherited, answered "already re-armed" for a tool that had never
19698+
* re-armed; the suppressed run then took the bypassed route and answered 401
19699+
* Bad credentials on every endpoint, `/rate_limit` included. A uniform 401 is a
19700+
* self-consistent story — the credential is dead — and it was read as one for
19701+
* about an hour against a channel that was alive the whole time. `guard` is
19702+
* therefore a parameter: a caller passes the name it actually sets, and only
19703+
* its own guard can stop its own re-exec.
19704+
*
19705+
* And a suppressed run must not be a SILENT one. `rearm: false, hint: false`
19706+
* prints nothing at any consumer, so the suppression left no line anywhere in
19707+
* the run log, which is the whole cost of that chain. This branch therefore
19708+
* sets `hint: true` — the one branch every consumer already prints — and its
19709+
* reason names the variable, the route and the 401 it would be mistaken for.
19710+
* It can only fire on the anomaly: a real re-exec's child carries `PROXY_FLAG`
19711+
* in its `execArgv` and is answered by the `routed` branch above, never here.
19712+
*
19713+
* @param {{ env?: Record<string,string|undefined>, execArgv?: string[], flagSupported?: boolean, guard?: string }} [ctx]
19714+
* @returns {{ rearm: boolean, hint: boolean, flag?: string, guarded?: string, reason: string }}
19715+
*/
19716+
export function proxyRearmPlan({ env = {}, execArgv = [], flagSupported = true, guard = PROXY_REARM_GUARD } = {}) {
1969119717
const { proxy, routed } = proxyRoute({ env, execArgv });
1969219718
if (!proxy) {
1969319719
return { rearm: false, hint: false, reason: 'no HTTPS_PROXY in the environment — node fetch reaches api.github.com directly' };
1969419720
}
1969519721
if (routed) return { rearm: false, hint: false, reason: `already routed through the proxy (${PROXY_FLAG} / NODE_USE_ENV_PROXY=1)` };
19696-
if (env[PROXY_REARM_GUARD] === '1') return { rearm: false, hint: false, reason: 'already re-armed once this run' };
19722+
if (env[guard] === '1') {
19723+
return {
19724+
rearm: false,
19725+
hint: true,
19726+
guarded: guard,
19727+
reason:
19728+
`${guard}=1 is set and this process is NOT routed through ${proxy}, so the one re-exec this run allows ` +
19729+
`was already spent and every request below bypasses the proxy — if they answer 401 Bad credentials, that ` +
19730+
`inherited guard is why, and unsetting ${guard} is the fix rather than a new token`,
19731+
};
19732+
}
1969719733
if (!flagSupported) {
1969819734
return { rearm: false, hint: true, reason: `this node does not accept ${PROXY_FLAG}; every request will bypass ${proxy}` };
1969919735
}
@@ -35334,6 +35370,32 @@ Doubles as the fire's **write self-check** (step 0). \`201\` is not the reading.
3533435370
t('proxy: …in NODE_OPTIONS, likewise', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', NODE_OPTIONS: `--enable-source-maps ${PROXY_FLAG}` } }).rearm, false);
3533535371
t('proxy: …and the env spelling NODE_USE_ENV_PROXY=1, likewise', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', NODE_USE_ENV_PROXY: '1' } }).rearm, false);
3533635372
t('proxy: the guard env stops an infinite re-exec loop', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).rearm, false);
35373+
// -- #18939: the guard is a NAME, and the plan reads the CALLER's. A sibling
35374+
// -- instrument's inherited guard answering "already re-armed" for a tool
35375+
// -- that never re-armed is how one variable replays the recorded
35376+
// -- "the credential is dead" hour: the suppressed run bypasses the proxy and
35377+
// -- answers 401 on every endpoint, and nothing is printed to say why.
35378+
t('proxy: the default guard is still THIS file\'s own name, so its own re-exec is unchanged', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).guarded, PROXY_REARM_GUARD);
35379+
t('proxy: a caller that names its OWN guard is suppressed by that one', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', OS_SIBLING_PROXY_REARMED: '1' }, guard: 'OS_SIBLING_PROXY_REARMED' }).rearm, false);
35380+
t('proxy: …and this file\'s name does NOT suppress it — the cross-suppression is gone', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' }, guard: 'OS_SIBLING_PROXY_REARMED' }).rearm, true);
35381+
t('proxy: …reported machine-readably, so a caller can name the variable to unset', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', OS_SIBLING_PROXY_REARMED: '1' }, guard: 'OS_SIBLING_PROXY_REARMED' }).guarded, 'OS_SIBLING_PROXY_REARMED');
35382+
// The landed mapper shape (`check-single-claim-paths.mjs`, `check-expected-skips.mjs`,
35383+
// `check-prior-rulings.mjs`) folds its own name onto the default and passes no
35384+
// `guard`. Both of its directions are pinned here, because the DEFAULT is what
35385+
// it relies on.
35386+
t('proxy: a mapper that maps its own name onto the default still suppresses', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).rearm, false);
35387+
t('proxy: …and maps an UNSET own name to undefined, which suppresses nothing', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: undefined } }).rearm, true);
35388+
// Silence is the whole cost of this chain: `rearm:false, hint:false` prints
35389+
// nothing at any consumer. The guard branch HINTS, which every consumer
35390+
// already prints — the ones that pass no `guard` of their own included.
35391+
t('proxy: a guard that suppresses SPEAKS rather than closing the route silently', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).hint, true);
35392+
t('proxy: …the line names the variable a reader has to unset', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).reason.includes(PROXY_REARM_GUARD), true);
35393+
t('proxy: …and names the 401 the silence would otherwise be read as', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' } }).reason.includes('401 Bad credentials'), true);
35394+
t('proxy: …and the proxy the request was supposed to take', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://127.0.0.1:40309', [PROXY_REARM_GUARD]: '1' } }).reason.includes('http://127.0.0.1:40309'), true);
35395+
// The runner leg, re-pinned ACROSS the new branch: a guard set where no proxy
35396+
// is configured must still spawn nothing, hint nothing and print nothing.
35397+
t('proxy: with no proxy configured the guard is inert (the Actions runner leg)', proxyRearmPlan({ env: { [PROXY_REARM_GUARD]: '1' } }).hint, false);
35398+
t('proxy: …and an already-routed run is answered by the ROUTE, not by the guard', Boolean(proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x', [PROXY_REARM_GUARD]: '1' }, execArgv: [PROXY_FLAG] }).guarded), false);
3533735399
// An older node gets a printed hint, never a bad-option crash.
3533835400
t('proxy: an unsupported flag hints instead of re-execing', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x' }, flagSupported: false }).hint, true);
3533935401
t('proxy: …and does not re-exec', proxyRearmPlan({ env: { HTTPS_PROXY: 'http://x' }, flagSupported: false }).rearm, false);

‎scripts/pm/label-write.mjs‎

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999

100100
import process from 'node:process';
101101
import { spawnSync } from 'node:child_process';
102+
import { readFileSync } from 'node:fs';
102103
import { fileURLToPath } from 'node:url';
103104

104105
import { isEntrypoint } from '../invoked-as.mjs';
@@ -788,6 +789,7 @@ async function driveOffline(argv, boardInit = {}) {
788789
// that stops registering cases is the bug; a pinned TOTAL would hide it the
789790
// moment a sibling battery grows (#13489, the shape post-stamped.mjs carries).
790791
const SELF_TEST_BATTERIES = Object.freeze({
792+
'the re-exec guard: the name this tool sets, and the patrol name that must not silence it': 11,
791793
'the arithmetic: union, difference, and the two idempotent no-ops': 12,
792794
'the ONE-OF refusal, and the one declared exception': 6,
793795
'the read-back verdict: three buckets, three different meanings': 7,
@@ -797,7 +799,7 @@ const SELF_TEST_BATTERIES = Object.freeze({
797799
'the remaining exits: refusal, mismatch, the ONE-OF block and the dry run': 5,
798800
'the CLI: what a typo must never be allowed to mean': 8,
799801
});
800-
const SELF_TEST_BATTERY_FLOOR = 8;
802+
const SELF_TEST_BATTERY_FLOOR = 9;
801803
const UNATTRIBUTED_BATTERY = '(unattributed)';
802804

803805
const batteryCases = new Map();
@@ -817,6 +819,31 @@ export async function selfTest() {
817819
};
818820

819821
// ── the arithmetic ────────────────────────────────────────────────────────
822+
// ── the re-exec guard (#18939) ────────────────────────────────────────────
823+
// The plan reads the guard name THIS file sets, never a shared one. A sibling
824+
// instrument's inherited guard used to answer 'already re-armed' here, and the
825+
// un-re-armed run then bypassed the proxy and answered 401 Bad credentials —
826+
// a false story about the credential, printed nowhere at all.
827+
battery('the re-exec guard: the name this tool sets, and the patrol name that must not silence it');
828+
{
829+
const PATROL_GUARD = 'OS_HALF_STATES_PROXY_REARMED';
830+
const proxied = { HTTPS_PROXY: 'http://127.0.0.1:40309' };
831+
const rearm = (env) => proxyRearmPlan({ env, guard: PROXY_REARM_GUARD, flagSupported: true });
832+
const own = { ...proxied, [PROXY_REARM_GUARD]: '1' };
833+
const ownSource = readFileSync(SELF_PATH, 'utf8');
834+
t('this tool\'s guard is its own name, never the patrol\'s', PROXY_REARM_GUARD !== PATROL_GUARD);
835+
t('…and the patrol name pinned here IS the plan\'s default, so a rename reds this battery', proxyRearmPlan({ env: { ...proxied, [PATROL_GUARD]: '1' } }).guarded === PATROL_GUARD);
836+
t('a proxied run with no guard set re-execs', rearm(proxied).rearm === true);
837+
t('…this tool\'s OWN guard is what stops the loop', rearm(own).rearm === false);
838+
t('…while the patrol\'s inherited guard does NOT suppress it', rearm({ ...proxied, [PATROL_GUARD]: '1' }).rearm === true);
839+
t('a suppressed run SPEAKS — silence is the whole cost of this chain', rearm(own).hint === true);
840+
t('…naming the variable a reader has to unset', rearm(own).reason.includes(PROXY_REARM_GUARD));
841+
t('…and naming the 401 the silence would otherwise be read as', rearm(own).reason.includes('401 Bad credentials'));
842+
t('the Actions-runner leg is unchanged: no proxy, no re-exec, no extra line', rearm({ [PROXY_REARM_GUARD]: '1' }).rearm === false && rearm({ [PROXY_REARM_GUARD]: '1' }).hint === false);
843+
t('structural: the dispatch really hands the plan THIS file\'s guard', /\n\s+guard: PROXY_REARM_GUARD,\n/.test(ownSource));
844+
t('structural: the plan is imported, not restated here', /\bproxyRearmPlan\b/.test(ownSource) && !/function\s+proxyRearmPlan\b/.test(ownSource));
845+
}
846+
820847
battery('the arithmetic: union, difference, and the two idempotent no-ops');
821848
const a1 = computeLabelTarget({ current: ['x', 'pm:queue'], add: ['pm:dispatched'], remove: ['pm:queue'] });
822849
t('target = (current ∪ add) − remove', a1.target, ['x', 'pm:dispatched']);
@@ -1012,17 +1039,24 @@ export async function selfTest() {
10121039
* credential problem and is not one.
10131040
*/
10141041
function rearmThroughProxy(args) {
1042+
// `guard` is THIS tool's own variable (#18939). Without it the plan read the
1043+
// patrol's shared name straight out of `process.env`, so a sibling
1044+
// instrument's inherited guard answered "already re-armed" here — silently —
1045+
// and the un-re-armed run then bypassed the proxy and answered 401 Bad
1046+
// credentials, a false story about the credential. The own-guard `if` that
1047+
// used to sit below was never reached for that case; the plan's own branch
1048+
// now covers it, and PRINTS the variable through `plan.hint`.
10151049
const plan = proxyRearmPlan({
10161050
env: process.env,
10171051
execArgv: process.execArgv,
10181052
flagSupported: process.allowedNodeEnvironmentFlags.has(PROXY_FLAG),
1053+
guard: PROXY_REARM_GUARD,
10191054
});
10201055
if (plan.hint) {
10211056
console.error(`ℹ️ ${plan.reason}. A refusal below may be about the route, not this container.`);
10221057
return null;
10231058
}
10241059
if (!plan.rearm) return null;
1025-
if (process.env[PROXY_REARM_GUARD] === '1') return null;
10261060
console.error(`ℹ️ re-exec with ${plan.flag}: ${plan.reason}.`);
10271061
const quiet = process.allowedNodeEnvironmentFlags.has('--disable-warning') ? ['--disable-warning=UNDICI-EHPA'] : [];
10281062
const child = spawnSync(process.execPath, [plan.flag, ...quiet, SELF_PATH, ...args], {

0 commit comments

Comments
 (0)