Repository navigation
fix(pm): the proxy re-exec guard is the caller's name, and it speaks when it suppresses - #19170
Conversation
…when it suppresses `check-governed-merges.mjs` carries its own `proxyRearmPlan` and read ONE hard-coded guard name — its own. `ci-failure.mjs` imports that plan and set the same variable on the child it re-execs, so either tool's guard, once inherited by the other, answered "already re-armed" for a run that had never re-armed. The suppressed run then took the bypassed route and answered 401 Bad credentials on every endpoint: a self-consistent "the credential is dead" story, printed nowhere at all. Both halves are closed, in the shape PR #18980 landed on the patrol's plan: - the plan takes `guard`, defaulting to this file's own name, and both live call sites hand it that name explicitly; - the guard branch SPEAKS — `hint: true`, `guarded: <name>` and a reason naming the variable, the proxy and the 401 it would be mistaken for; - `rearmProxyOrNull` prints the hint instead of returning through the silent `!rearm` line, so every mode says it bypassed the proxy, not only the sweep's INCOMPLETE banner; - `ci-failure.mjs` declares and sets its OWN `OS_CI_FAILURE_PROXY_REARMED`, and imports the sibling's spelling as `GOVERNED_MERGES_GUARD` so its self-test can pin the two apart. The copy itself stays a copy, and the reason is measured rather than assumed. A module-scope import of the owner's plan exits 13 ("Detected unsettled top-level await" at `check-half-states.mjs`'s `GOVERNED_REGISTER`) against a control exit 3, and a lazy `await import()` inside `selfTest()` exits 13 too, because this file's `--self-test` dispatch is itself a module-scope await. So the duplication is declared a MIRROR and pinned to its owner the way `CONTRACT_REVIEW_LABEL` is: by reading that file's source off disk, plus the REASON, so the next divergence reds instead of happening silently. Both self-tests pin both directions: own guard suppresses, the sibling's name does not, and the suppressed branch names its suppressor. Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2 Co-authored-by: Claude <noreply@anthropic.com>
The success line is what a reader of a green run is handed, and it enumerated every other pinned set. Adds the guard clause: the caller's name, the speaking suppression, the inert runner leg, and the mirror pinned to its owner's source. ⛔ No backticks in that template literal — one terminates it and the file stops parsing (caught here by `node --check` before the gate ran). Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2 Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: In-seat review by the ① Derived judgments
② Semver levelNone owed — ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #18989
Clause-②: no
scripts/pm/check-governed-merges.mjscarries its ownproxyRearmPlanand that plan read ONEhard-coded guard name — this file's.
scripts/pm/ci-failure.mjsimports that plan and set thesame variable on the child it re-execs. So either tool's guard, once inherited by the other,
answered "already re-armed" for a run that had never re-armed; the suppressed run then took the
bypassed route and answered 401 Bad credentials on every endpoint, with no line printed anywhere
to say why. That is the #18939 mechanism in a second plan — the one PR #18980 does not touch —
and, for the plan itself, one route with two owners.
What changed, per file
scripts/pm/check-governed-merges.mjsproxyRearmPlan({ …, guard = PROXY_REARM_GUARD } = {})— the guard is the CALLER's name, andthe default stays this file's own, so its own re-exec is unchanged.
hint: true,guardednaming the suppressor, and a reason namingthe variable to unset, the proxy the request was supposed to take, and the 401 the silence
would otherwise be read as. It can only fire on the anomaly — a real re-exec's child carries
the flag in
execArgvand is answered by the branch above.rearmProxyOrNull, and the INCOMPLETE banner's plan) hand itguard: PROXY_REARM_GUARDexplicitly: for a plan another tool imports, the default is not thecontract.
rearmProxyOrNullnow PRINTS the hint instead of returning through the silent!rearm.rearmline. Both hint branches — an older node, and a guard already set — previously reached a reader
only through the sweep's INCOMPLETE banner, and only when the incompleteness was about
attribution; every other mode bypassed the proxy in silence.
scripts/pm/ci-failure.mjsOS_CI_FAILURE_PROXY_REARMED, spelled once, and hands it to the planper call. The re-exec already spawned under that identifier, so the child now inherits this
tool's name and nobody else's.
GOVERNED_MERGES_GUARD, so the self-test pins the two apartagainst the real constant rather than a hand-copied string: a rename on either side reds.
The arm taken: the copy STAYS, and the reason is measured rather than assumed
The card asks the taker to keep the copy only if the header's stated verbatim-copy reason still
holds, and to delete it and import the patrol's plan otherwise. Both halves were measured.
check-governed-merges.mjs's header states noverbatim-copy reason, and the plan's own docblock said only "Pure, so every branch is
offline-testable". The copy-verbatim argument lives in
check-half-states.mjs— it is aboutcopying THAT file into a sibling repo, and it bars the reverse edge, not this one.
2026-09-19 on a throwaway copy of
scripts/outside the repo (nothing in the tree under testwas mutated):
check-governed-merges.mjs --test AGENTS.mdimport { proxyRearmPlan } from './check-half-states.mjs'at module scopeconst patrol = await import('./check-half-states.mjs')insideselfTest()--self-testawait selfTest()The cycle is short and live:
check-half-states.mjs:10044resolves its H43 register at modulescope with
await loadGovernedRegister(), which awaitsimport('./check-governed-queue-guard.mjs'), which imports THIS file at module scope. The lazydoor that
check-governed-queue-guard.mjs'sCONTRACT_REVIEW_LABELdescribes is shut here too,because this file's
--self-testdispatch is itself a module-scope await. Importing wouldtherefore not tidy the duplication, it would take
check:pm-governed-merges— the governed-surfaceinstrument in most derived unions — down with it, plus the patrol and the queue guard.
So the copy stays, and the triage's 「修法若只统一守卫名而不处理副本,下一次分叉照旧发生」 is answered by
DECLARING it: the plan's docblock names its owner, states both measured exits, and the self-test
pins the mirror to that owner the way
CONTRACT_REVIEW_LABELis pinned — by reading the owner'sSOURCE off disk (a constant asserting against itself proves nothing), and by pinning the REASON,
so when the patrol's module-scope await goes away the failing case says to make this a real import.
Escalation condition re-measured in the worktree:
grep -rn 'function proxyRearmPlan' scripts/returns exactly 2 definitions (
check-governed-merges.mjs,check-half-states.mjs). No thirdcopy, so the deliverable stayed as graded.
The A/B — the card's own repro, read-only and offline
proxyRearmFor(['--pr','9774'], { env: { HTTPS_PROXY, GUARD: '1' } })through a harness thatimports the module (no dispatch, no socket, no argv). Leg A is the base files restored with
git checkout HEAD~1 --, leg B this branch; the restore was verified byte-identical(
git hash-objectequals the HEAD blob for both files,git diff HEADempty).rearm: truerearm: trueOS_GOVERNED_MERGES_PROXY_REARMED=1rearm: false, hint: false, reason "already re-armed once this run" — the silent cross-suppressionrearm: true— the sibling's guard no longer silences this toolOS_CI_FAILURE_PROXY_REARMED=1rearm: false, hint: true, guarded: OS_CI_FAILURE_PROXY_REARMED, reason naming the variable, the proxy and the 401OS_HALF_STATES_PROXY_REARMED=1rearm: true— the patrol's name is inert here tooLeg A is the defect exactly as filed: a tool that never re-armed, told it had, printing nothing.
Self-tests: both directions, both sides
check-governed-merges.mjs --self-testci-failure.mjs --self-testproxyRearmFor: the transport trapbatteryThe ci-failure count is measured, not asserted: with that floor mutated 17 to 18 through
scripts/ablation-replace.mjs, the run reds with "registered 17 case(s), below its pinned floorof 18" (exit 1), and the tool restored the file to a blob equal to HEAD with
git diff HEADempty. A first attempt at that probe passed the wrong flags, printed usage and exited 0 without
mutating anything — a no-op reading, discarded and re-run rather than believed.
Gates
Derived in the worktree with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(the script takes the change set itself; the derivation names the repo and commitd904ab5) — 32 families, all 32 run, every exit code captured before any pipe. Reconciled with--ran: "32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED (a DERIVED zero — all 32recorded an exit code and none of them is 3)". Every one exited 0, including
pnpm check:pm-governed-merges(this file's own self-test, 410 assertions),ci-failure.mjs --self-test,check-self-test-wired,check-self-test-workflow-commands,check-scripts-symbol-anchors,check-declaration-mirrorsandcheck:nul-bytes.pnpm check:pm-dispatch-gatestook 1025.1s on this box (1866 cases pass) — well past the430-450s the dispatch estimated, worth knowing for anyone budgeting a foreground run while other
agents share the container.
Repo-wide sweeps (
pnpm lintand the rest of the farm) are CI's run, not this branch's. A scanfor control bytes over both files (
grep -naPover the C0 set plus DEL) exits 1 with no hits.Acceptance notes
execArgv/NODE_OPTIONS,while its owner also accepts
NODE_USE_ENV_PROXY=1. A run already routed by the env spellingtherefore re-execs once needlessly here — a spent process, never a wrong verdict — so it is a
mirror divergence rather than a defect class. Whoever next edits either plan meets it: the new
battery names the owner and reads its source, which is where the divergence is now visible.
testVerdict,landsByHumanMerge, the size limb and the post-mergedefault-mode listing of
check-governed-merges.mjsare [finding] the merge-queue guard (check-governed-queue-guard.mjs, merge_group leg) reads the PATH register only — a PR over 5,000 changed lines that a seat enqueues still merges; the size limb #19012 adds to the seat-side pre-check never reaches the queue #19036's region. Every hunk here is inthe proxy plan, its constants, its two call sites and the self-test batteries.
skip-changeset:scripts/pm/**is published by no package'sfiles[].Generated by Claude Code