Skip to content

fix(scripts): check-regen-pending --self-test no longer routes its stubs through an ambient package manager - #16009

Draft
zhuangjianguo wants to merge 1 commit into
mainfrom
claude/issue-15990-regen-selftest-launcher-independence
Draft

fix(scripts): check-regen-pending --self-test no longer routes its stubs through an ambient package manager#16009
zhuangjianguo wants to merge 1 commit into
mainfrom
claude/issue-15990-regen-selftest-launcher-independence

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes #15990

check:merge-driver runs node scripts/check-regen-pending.mjs --self-test. Its
deferred-merge fixture wrote a stub package.json into a mkdtemp directory under
tmpdir() and ran the stub gate 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, not by the tree.

The differentiator, and the reading that settles it

The card asks why origin/main and four unrelated PRs were green while one PR was red
twice. They are not different. The verdict is per-JOB and environmental, and main is
red 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 the
card 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":

  1. .github/actions/setup-pnpm runs corepack install in the workspace. Measured with a
    scratch COREPACK_HOME: that materialises the pinned pnpm 10.31.0 and writes no
    lastKnownGood.json.
  2. The fixture's temp dir pins nothing, so Corepack ignores the repo pin and resolves
    pnpm's latest dist-tag from the registry. Measured: it downloads pnpm 12.3.4
    (latest since 2026-08-26; 12.3.4 published 2026-09-04T14:20Z).
  3. pnpm 12's CLI rejects -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.
  4. Corepack then writes lastKnownGood.json, and it is sticky: the warm re-run costs
    116 ms with no network. That matches the failing job's cadence exactly — every
    runHook in the CI log takes ~130 ms, against 330 ms measured here for the pinned
    pnpm 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.
  5. actions/cache@v6 saves COREPACK_HOME back on a miss, so a job restores a store
    carrying either a -s-accepting pnpm (green) or a pnpm 12 (red). Which one is not a
    fact 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/caches answers
403 unauthenticated.

What changed

The gate takes its launcher from OS_REGEN_GATE_LAUNCHER. Production leaves it unset and
every gate still runs as pnpm -s SCRIPTNAME, byte-identical to today. --self-test sets
it — 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 for
check:cross-package-test-inputs: a detector with no dependencies cannot itself fail to
resolve in CI.

Two details worth review:

No case is skipped, disabled, quarantined or relaxed. Two cases are ADDED, pinning the
diagnosis technique permanently: a hostile pnpm first on PATH, refusing the way pnpm 12
refuses, 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
f7db8f4fd blob, verified on disk by git hash-object before measuring; the restore leg
verified back to the HEAD blob with git diff HEAD and git status --porcelain both
empty.

launcher on PATH pre-fix post-fix
real pnpm 10.31.0 exit 0 exit 0
refuses, exit 1 (Corepack-style) exit 1, 11 red assertions exit 0
pnpm 12 shim: rejects -s, exit 2 — the measured CI shape exit 1, 11 red assertions exit 0
no pnpm anywhere on PATH exit 1, 12 red assertions exit 0

Gates 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 derived
with 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.mjs still classifies this file ROSTER, unchanged, and the
battery roster and floor are untouched. pnpm lint was NOT run: this worktree has no
node_modules, so no narrowing is claimed for it — CI's Lint & Repo Gates owns it.

Clause-②: no — the diff is one repo gate script; git diff against the merge base shows
no 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 the
production gate command is unchanged.

No changeset: scripts/** repo tooling, root manifest is private, nothing ships.


🤖 Generated with Claude Code

https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ


Generated by Claude Code

…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants