fix(scripts): check-regen-pending --self-test no longer routes its stubs through an ambient package manager - #16009
Conversation
…ubs through an ambient package manager The fixture wrote its gate stub into a mkdtemp directory under tmpdir() and ran it as `pnpm -s check:spec-changes`. That directory declares no `packageManager` and has no parent manifest to inherit one from, so the verdict was decided by whatever launcher resolved there rather than by the tree. Measured against CI's own Corepack store: `.github/actions/setup-pnpm` materialises the pinned pnpm and writes no `lastKnownGood.json`, so in a directory with no pin Corepack ignores the pin and resolves pnpm's `latest` dist-tag instead. That is pnpm 12, whose CLI rejects `-s` outright (`error: unexpected argument '-s' found`, exit 2). Every stub then collapsed to "the gate exited non-zero", which the script correctly grades as `stale`, so exactly the cases whose expected outcome was not stale went red -- byte identically on an innocent PR and on origin/main's own push build. The gate now takes its launcher from `OS_REGEN_GATE_LAUNCHER`, which production leaves unset (`pnpm -s`, unchanged) and the fixture points at a launcher it writes itself: it reads the named script out of the manifest at cwd and runs the body under /bin/sh with argv0 `sh`, the same producer and the same diagnostic shapes the classifier already anchors on. No case is skipped, disabled or relaxed; two cases are ADDED, asserting that a hostile pnpm first on PATH leaves both the `stale` and the `clean` readings byte-identical. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
|
Closing this PR. #16002 landed and fixes the measured failure; this one's residue is recorded rather than merged. This is the claimant's call, which the Why closed rather than re-scoped as a deltaThe two fixes are not equivalent, and the difference is real but narrow:
The delta is exactly one measured condition: no That condition does not arise in CI. The cache channel that made today's failure sticky — What is preserved, so this is not thrown awayThe round's four-condition matrix is the useful artifact and it is recorded on #15990. For the record here:
Also worth keeping: the round added two cases pinning a hostile pnpm first on ⛔ Nothing here is a criticism of the work. The reason this closes is sequencing and duplication, not quality. Generated by Claude Code |
Fixes #15990
check:merge-driverrunsnode scripts/check-regen-pending.mjs --self-test. Itsdeferred-merge fixture wrote a stub
package.jsoninto amkdtempdirectory undertmpdir()and ran the stub gate aspnpm -s check:spec-changes. That directory declaresno
packageManagerand has no parent manifest to inherit one from, so the verdict wasdecided by whatever launcher resolved there, not by the tree.
The differentiator, and the reading that settles it
The card asks why
origin/mainand four unrelated PRs were green while one PR was redtwice. They are not different. The verdict is per-JOB and environmental, and
mainisred too — its own push build, run 33981169123 / job 101346594800, 2026-09-05T17:51Z,
fails with the byte-identical signature: the same eleven red assertions, the same three
interleaved greens, the same
1 failure(s) (cases and floor)summary. The greens on thecard were sampled before that crossover, the reds after. No tree-level or config-level
differentiator exists to find.
The mechanism, measured rather than inferred — and it is not "the launcher fails to
resolve":
.github/actions/setup-pnpmrunscorepack installin the workspace. Measured with ascratch
COREPACK_HOME: that materialises the pinned pnpm 10.31.0 and writes nolastKnownGood.json.pnpm's
latestdist-tag from the registry. Measured: it downloads pnpm 12.3.4(
latestsince 2026-08-26; 12.3.4 published 2026-09-04T14:20Z).-s:error: unexpected argument '-s' found, exit 2.Every stub then collapses to "the gate exited non-zero", which the script correctly
grades as
stale— so exactly the stubs whose expected outcome is not stale go red.lastKnownGood.json, and it is sticky: the warm re-run costs116 ms with no network. That matches the failing job's cadence exactly — every
runHookin the CI log takes ~130 ms, against 330 ms measured here for the pinnedpnpm 10.31.0 running the same command in the same directory. The job's launcher
resolved and ran; it just answered hostilely. Consistent with this, the job log shows a
Corepack cache hit and contains no download line.
actions/cache@v6savesCOREPACK_HOMEback on a miss, so a job restores a storecarrying either a
-s-accepting pnpm (green) or a pnpm 12 (red). Which one is not afact about any diff. This reconciles the two observations that a bare "sometimes fails
to resolve" cannot hold together: flaky across jobs, byte-identical within one.
Not determinable from outside the runner, and stated rather than left silent: which job
first seeded a poisoned store, and on which cache scope.
GET /actions/cachesanswers403 unauthenticated.
What changed
The gate takes its launcher from
OS_REGEN_GATE_LAUNCHER. Production leaves it unset andevery gate still runs as
pnpm -s SCRIPTNAME, byte-identical to today.--self-testsetsit — the only thing that ever does — to a launcher the fixture writes for itself, which
reads the named script out of the manifest at the gate directory and runs its body under
/bin/sh. This is direction 1 on the card, and the principle AGENTS.md already states forcheck:cross-package-test-inputs: a detector with no dependencies cannot itself fail toresolve in CI.
Two details worth review:
/bin/shby absolute path with argv0sh.execSyncwouldhave produced
/bin/sh: 1: NAME: not found, whichgateCouldNotRun()'s regex does notmatch — the runner name drops out of the diagnosis while exit 127 still classifies. That
regression showed up as a red assertion during this work and was fixed by making the
fixture reproduce production's producer shape, not by widening the matcher.
swept into the index by the
git add -Aonside2and re-open the stat-dirtymerge --abortcrash of [finding] check-regen-pending.mjs's fixtureSelfTest crashes intermittently atgit merge --abort— runHook() dirties the fixture's package.json while a merge is in progress #9258.No case is skipped, disabled, quarantined or relaxed. Two cases are ADDED, pinning the
diagnosis technique permanently: a hostile
pnpmfirst on PATH, refusing the way pnpm 12refuses, with each reading asserted equal to the same call made without it.
Proof
Reproduced red first, then green under a real, a hostile and an absent launcher. Exit
codes captured by redirect-then-read, never after a pipe. The pre-fix leg restored the
f7db8f4fdblob, verified on disk bygit hash-objectbefore measuring; the restore legverified back to the HEAD blob with
git diff HEADandgit status --porcelainbothempty.
-s, exit 2 — the measured CI shapepnpmanywhere on PATHGates run locally on the final commit
5a2a2092f, each exit code read before any pipe —check:merge-driver(the gate this card is about),check:nul-bytes,check:parse-guard,check:entry-guard,check:cross-package-test-inputs,check:pnpm-filter-targets,check:agent-test-spelling,check:bash32-floor: all exit 0. The family was derivedwith
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(54 families,nearly all
scripts/**-wide); the rest is CI's single pass over the farm.scripts/measure-self-test-floor.mjsstill classifies this file ROSTER, unchanged, and thebattery roster and floor are untouched.
pnpm lintwas NOT run: this worktree has nonode_modules, so no narrowing is claimed for it — CI'sLint & Repo Gatesowns it.Clause-②: no— the diff is one repo gate script;git diffagainst the merge base showsno
packages/spec/api-surface/movement (positive control on the same command and scope:scripts/returns the changed file), nothing is published from any package, and theproduction gate command is unchanged.
No changeset:
scripts/**repo tooling, root manifest isprivate, nothing ships.🤖 Generated with Claude Code
https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
Generated by Claude Code