Skip to content

fix(devx): npm 10 crashes with edgesOut on the vendor-export probe and the blank scaffold — the probe installs with a pinned npm 11, the template overrides vitest - #23799

Draft
objectstack-fleet[bot] wants to merge 5 commits into
mainfrom
claude/issue-23788-npm10-edgesout
Draft

objectstack-fleet[bot] wants to merge 5 commits into
mainfrom
claude/issue-23788-npm10-edgesout

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23788

Clause-②: no

The Version Packages PR #23481 was red on two lockfile-less npm installs. Once those passed, a third install failed: the runtime image's global install. All three crash npm 10 with npm error Cannot read properties of null (reading 'edgesOut'). This PR fixes all three on main:

  • the vendor-export --resolve probe now installs with a pinned npm 11;
  • the blank scaffold now ships "overrides": { "vitest": "~4.0.0" };
  • the official runtime image (docker/Dockerfile) now runs its npm install -g under a pinned npm 11 (see Patch round below).

Nothing here touches changeset-release/main, a workflow, .npmrc, pnpm-workspace.yaml or pnpm-lock.yaml. The monorepo's own pnpm install is unaffected.

The trigger

vitest@4.1.12, published 2026-10-15T14:51:24Z under the V4 dist-tag:

  • Every other @vitest/* peer of 4.1.12 is pinned to exactly 4.1.12, but it declares "@vitest/browser-webdriverio": "^4.1.11".
  • @vitest/browser-webdriverio@4.1.12 was never published; the newest 4.x is 4.1.11, last modified 2026-09-08.
  • The only match, 4.1.11, peers "vitest": "4.1.11" exactly. The peer set therefore contradicts itself.
  • npm 10's arborist detaches a node while loading it and then dereferences node.parent (null) in #loadPeerSet (build-ideal-tree.js:1289 on npm 10.9.4).
  • npm 11 handles it.

Both installs reach vitest only through OPTIONAL peers: better-auth@1.7.3 peers vitest: ^2 || ^3 || ^4, and @objectstack/metadata-core peers vitest: ^4.0.0. On npm 10, each resolves to vitest 4.1.12.

Bisected with npm's --before, npm 10.9.9, each leg in an empty directory with a fresh --cache:

install --before=2026-10-15T08:36:00Z --before=2026-10-15T14:50:00Z --before=2026-10-15T14:52:00Z
npm install better-auth@1.7.3 --no-package-lock (the probe) exit 0 exit 0 exit 1, edgesOut
blank template, @objectstack/* set to latest not run exit 0 exit 1, edgesOut

The issue's hypothesis holds in kind (an upstream publish in the window brings back npm 10's #loadPeerSet crash). The path is different from #22912's: there the root vitest reached vitest@* through vite 8's devtools peers. Here an optional peer meets one release's inconsistent peer set. The npm side has no fix: 10.9.9 is the newest npm 10. Upstream has no fix either: re-checked at the time of this PR, there is still no @vitest/browser-webdriverio@4.1.12 and no vitest@4.1.13.

Before: reproduced

Node 22, each leg in an empty directory outside any checkout with a fresh --cache:

leg npm 10.9.4 npm 10.9.9 npm 11.21.0 npm 11.21.1
(a) npm install better-auth@1.7.3 --no-package-lock --no-audit --no-fund in the probe's package.json exit 1, edgesOut exit 1, edgesOut exit 0 exit 0
(a) pnpm check:vendor-export-contract-resolve at the base commit exit 3, PREREQUISITE NOT MET, edgesOut exit 3, same not run not run
(b) the CI step replayed on a fresh repo scaffold, main's stamp ^18.0.0-next.1 not run first install exit 1 edgesOut; fallback exit 1 edgesOut PASS not run
(b) the same with the release branch's stamp ^18.0.0-next.2 (ETARGET, then fallback to latest = 17.7.0) exit 1, edgesOut (template with latest) fallback exit 1, edgesOut PASS exit 0 (template with latest)

Isolated manifests on npm 10.9.9, for reach: {"dependencies":{"@objectstack/plugin-auth":"latest"}}, {"dependencies":{"@objectstack/metadata-core":"latest"}} and the os init plugin manifest (@objectstack/spec, @objectstack/cli, typescript ^5.3.0, vitest ~4.0.0) each exit 1 with edgesOut. The blank template with its vitest devDependency removed still crashes, so the template's own vitest range is not the path in, and no range change there can fix it.

The repair, and why each route

(ii) The probe: scripts/check-vendor-export-contract.mjs. --resolve measures a vendor's export surface. It does not measure peer resolution. The probe now fetches npm@11.21.0 (PROBE_INSTALLER_NPM, exact, at least two weeks old) into its scratch directory once per run, using the runner's npm. npm bundles its dependencies, so that install walks no peer graph. Every vendor install then runs under it.

  • Failing to fetch the installer is still PREREQUISITE NOT MET (exit 3).
  • A vendor that npm 11 cannot install is still PREREQUISITE NOT MET.
  • Only the npm-10 crash class stops masking the measurement.
  • The verdict line names the installer: VERDICT: PASS — vendor export contract (registry resolution, installed with npm@11.21.0).

Measured alternatives, not taken:

  • --legacy-peer-deps also skips REQUIRED peers. @better-auth/core@1.7.3 requires better-call, jose, kysely and others, and @better-auth/core/context then fails to import (Cannot find package 'better-call', npm 10.9.9). That would be a false "does not load" red.
  • --omit=peer --omit=optional still exits 1: the crash happens while the ideal tree is built.

The self-test now pins two things: the installer is exact with major 11 or later, and the probe's install argv is whole, so a flag that changes which packages land cannot creep in.

(i) The blank template: packages/create-objectstack/src/templates/blank/package.json. A user-facing manifest is affected. npx create-objectstack falls back to npm when pnpm is absent, and Node 22 bundles npm 10, so every such user's first npm install crashed. The template now ships "overrides": { "vitest": "~4.0.0" }, equal to its own devDependencies.vitest. It routes every vitest edge in the npm graph to the project's declared range.

  • Same tree as npm 11 without the override. The installed tree (path plus name@version for every package) is identical to npm 11.21.0's without the override: 566 packages on main's stamp, 563 on the fallback, 0 diff lines across npm 10.9.4, 10.9.9, 11.21.0 and 11.21.1.
  • pnpm ignores the field. pnpm does not read npm's overrides (measured on pnpm 10.28.0), so pnpm installs are unchanged.
  • ⛔ Not "$vitest". It also installs, but npm then stops hoisting @objectstack/metadata-core and nests 17 copies of it (measured on npm 10.9.4, 10.9.9 and 11.21.0; on 10.9.9, a scoped {"@objectstack/metadata-core":{"vitest":"$vitest"}} and a version-keyed override do the same).
  • The literal's cost is loud. npm refuses an override that differs from the direct dependency: EOVERRIDE: Override for vitest@^4.1.0 conflicts with direct dependency, measured on npm 10.9.9 and 11.21.0. The new template test pins overrides.vitest === devDependencies.vitest, so the two move together. The changeset tells users the same.

(iii) Workflows: not needed. All three repairs are in non-governed files, so this PR touches no governed surface.

After

leg npm 10.9.4 npm 10.9.9 npm 11.21.0 npm 11.21.1
pnpm check:vendor-export-contract-resolve (HEAD, fresh npm cache) exit 0, PASS, 2 edges exit 0, PASS, 2 edges not run not run
(b) fresh repo scaffold, main's stamp, the CI step replayed PASS, first install PASS, first install PASS PASS
(b) the same with the release stamp, ^18.0.0-next.2, via the fallback PASS (ETARGET, then fallback exit 0) PASS (same) PASS (same) PASS (same)

The after-PASS lines: ✓ @better-auth/core/context @ @better-auth/core@1.7.3 (via "1.7.3", 2 symbol(s)) and ✓ better-auth/adapters @ better-auth@1.7.3 (via "1.7.3", 1 symbol(s)).

Tests

  • pnpm --filter create-objectstack test: 18 files, 275 tests, passing.
  • pnpm --filter create-objectstack typecheck: exit 0, after building the dependency closure. tsc --listFiles includes template-consistency.test.ts.
  • node scripts/check-vendor-export-contract.mjs --self-test: OK. node scripts/sync-scaffold-emission-policy.mjs --check and node scripts/sync-template-versions.mjs --check: exit 0.
  • Ablation of the new template test, committed state first, mutation through scripts/ablation-replace.mjs with an anchor that must hit, restored from HEAD and proven by blob hash:
    • with the overrides block deleted (on-disk count 1 → 0, JSON still parses), template-consistency.test.ts reads 1 failed | 38 passed, and the failure is the new case;
    • restored, it reads 39 passed.
    • The test reads src/templates from disk, so no dist/ is involved.
  • pnpm check:docs-image-tag (patch round): self-test 118 assertions, then OK, 2 driver(s) installed by docker/Dockerfile compared against 2 promised in docker/README.md (2 matched by name).

Patch round: the runtime image (docker/Dockerfile)

With the probe and the scaffold fixed, Scaffold with repo dist (job 116797775725, head c9134933e) got as far as building the official runtime image from this checkout. It then failed at line 69 of that file, RUN npm install -g @objectstack/cli@${OS_CLI_VERSION} "pg@^8.0.0" "mysql2@^3.0.0" && npm cache clean --force, with the same edgesOut crash on node:22-slim's npm.

  • A global install reads no project overrides, so round 1 could not reach this line.
  • docker-publish.yml ships this image, so the same line would also have broken the release's image publish.

The change. The same RUN now does four things:

  1. Fetches npm@11.21.0 into /tmp/os-installer-npm with the image's own npm. npm bundles its dependencies, so that fetch walks no peer graph.
  2. Puts it first on PATH for that RUN only, and checks test "$(npm --version)" = "11.21.0".
  3. Runs the unchanged npm install -g @objectstack/cli@${OS_CLI_VERSION} "pg@^8.0.0" "mysql2@^3.0.0".
  4. Runs npm cache clean --force, then deletes the installer in the same layer.

The version is the one PROBE_INSTALLER_NPM holds for the same crash. The installed packages and their ranges are unchanged, and the image still ships node:22-slim's npm.

Why this mechanism. Measured inside node:22-slim (node v22.23.3, npm 10.9.9, global prefix /usr/local):

  • Same install location. Run from /tmp, the fetched npm 11 resolves first on PATH and uses the same global prefix: npm config get prefix gives /usr/local and npm root -g gives /usr/local/lib/node_modules. So the install lands where the image's npm would put it.
  • No image growth. The installer is 20 MB. Deleting it in the same RUN keeps it out of every layer: the install layer is 666 MB, against 663 MB for the pre-trigger control below.
  • Not taken:
    • npm install -g npm@… replaces the image's npm for every user of the image.
    • npx npm@… leaves 20 MB in ~/.npm/_npx after npm cache clean --force (measured).
    • --legacy-peer-deps also skips required peers (measured on the probe).

The gate. pnpm check:docs-image-tag anchored the driver line on the first trimmed line starting RUN npm install -g. The new RUN opens with the installer fetch, so on the new Dockerfile the old parse went red with DRIVER-ANCHOR-LOST (measured). The new parse:

  • anchors on npm install -g after a RUN or && leader;
  • drops the leader, then cuts at the next &&;
  • does not anchor on the installer fetch, which has no -g.

On this tree it reads line 90 (&& npm install -g \) and finds pg@^8.0.0 and mysql2@^3.0.0, matching 2 against 2 in docker/README.md. docker/README.md is unchanged. The new self-test cases, on the image's exact shape:

  • the anchor is the driver line, not the installer line, and npm@11.21.0 is not read as a driver;
  • that shape, agreeing with the table, is green with 2 compared against 2;
  • a drifted range in that shape is refused (DRIVER-RANGE-MISMATCH);
  • an npm upgrade spelled as a global npm install -g npm@… ahead of the drivers is read as the install line and refused (DRIVER-UNDOCUMENTED), never skipped into a green.

pnpm check:docs-image-tag exits 0, self-test 118 assertions.

Readings

Setup. A private dockerd 29.8.2 in this container.

  • node:22-slim came from the GCR and ECR mirrors of Docker Hub. Both serve index digest sha256:c3de60bf…; Docker Hub itself answered 429.
  • The only addition on top is the proxy CA as NODE_EXTRA_CA_CERTS, so npm inside the build can reach the registry.
  • Each build used the Dockerfile byte for byte, the context docker/ as CI uses it, and --no-cache.
build OS_CLI_VERSION=17.7.0 OS_CLI_VERSION=18.0.0-next.2
before: c9134933e's Dockerfile exit 1, npm error Cannot read properties of null (reading 'edgesOut') exit 1, same
after: d19cc5888's Dockerfile exit 0, added 525 packages exit 0, added 528 packages (on a working copy whose RUN was byte-identical; only a comment changed before the commit)
control: the before Dockerfile, npm limited to --before=2026-10-15T14:50:00Z exit 0, added 525 packages not run

After image (d19cc5888, 17.7.0) against the pre-trigger control:

reading control (pre-trigger, npm 10.9.9) after
os --version @objectstack/cli/17.7.0 linux-x64 node-v22.23.3 same
the image's npm --version 10.9.9 (/usr/local/bin/npm) same
global top level @objectstack corepack mysql2 npm pg same
pg / mysql2, resolved from the image's driver-sql pg@8.23.1, mysql2@3.24.6 same
docker-publish.yml's smoke: os --version, then node -e "require('pg'); require('mysql2')" from the global tree exit 0, exit 0 exit 0, exit 0
/tmp/os-installer-npm absent absent
global tree (path plus name@version) 722 packages 722 packages, with 3 version differences (below)

The 3 version differences were each published after the control's cutoff: @mongodb-js/saslprep@1.6.1 (10-15 19:13Z), argparse@3.0.3 (21:58Z) and js-yaml@5.5.0 (23:28Z). Neither image contains vitest.

  • Extend example. The README's extend-the-image example (USER root, then RUN npm install -g tedious) runs on the image's own npm 10.9.9 and installs on the after image (exit 0).
  • NOT MEASURED: linux/arm64, the second platform docker-publish.yml builds. The mechanism is architecture-independent JavaScript.

Gates (local, on head d19cc5888)

  • Measured tree. The branch is behind origin/main (74b436263). On the branch tree, dispatch-gates flagged it as STALE: 9 of the files it derives from changed on main since the branch point. So the union ran on a scratch commit, 29e3f931e: origin/main with this branch merged locally and never pushed. It differs from main by exactly this PR's 6 files, the same shape as the merge ref CI tests.
  • Derivation. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack named 96 commands: round 1's 95, plus pnpm check:docs-image-tag.
  • Result. Every package was built first, under the verify lock (74 of 74 tasks). Each command then ran byte for byte with its exit code captured before any pipe, and all 96 exited 0 on the first pass.
  • Reconciliation. --ran: 96 derived, 96 run, 0 NOT-MEASURED, 0 UNRUN.
  • Governed surfaces. node scripts/pm/check-governed-merges.mjs --branch claude/issue-23788-npm10-edgesout (the register on both the branch tree and main's): 0 of 6 paths hit the register. NOT governed, 335 changed lines.
  • CI on d19cc5888 (one read at 04:27Z): all 43 check runs completed. Every one is success or skipped by its selector, including Scaffold with repo dist, Validate Package Dependencies, Check Changeset, Lint & Repo Gates and Test Core.
  • Left to CI. The full farm, the 11 wide-population families and the workflow-valued families are CI's.

What #23481 needs afterwards

Nothing is pushed to changeset-release/main. release.yml's version-pr job regenerates that PR on its 6-hourly schedule (cron 0 */6 * * *), or on the relay's refresh_version_pr: true dispatch. It does not run on a push to main. Once this lands, the next regeneration carries all three fixes. A re-run of the PR's bot-triggered checks is a maintainer act. A plain re-run of the existing red run reuses that run's old commit, so it cannot see this fix.

main's own schedule run of validate-deps.yml, run 38843597390 at 03:03Z on 46ca793edb, is red at the same step: "Verify the declared ranges cannot resolve past our import surface", exit 3, which is PREREQUISITE NOT MET. So this is not specific to the release branch.

Acceptance notes

These are related findings, not fixed here:

  • os init / os create emit the same unfixed manifest class. The os init plugin manifest crashes npm 10.9.9 with edgesOut (measured). Those emitters build package.json in packages/cli/src/commands/init.ts / create.ts, and create.ts is outside this claim's surface.
  • Existing npm-10 projects that depend on any @objectstack/* package reaching @objectstack/metadata-core or @objectstack/plugin-auth crash the same way until upstream publishes a consistent vitest 4.x (measured: @objectstack/plugin-auth@latest and @objectstack/metadata-core@latest each alone). npm 11 installs them.
  • The template override is a hand-kept copy. overrides.vitest copies SCAFFOLD_VITEST_RANGE by hand, guarded by the new test. A POLICY_STAMPS row in scripts/sync-scaffold-emission-policy.mjs would generate it. That script is outside this claim's surface.
  • The self-hosting guide's self-built runtime crashes the same way. content/docs/deployment/self-hosting.mdx offers an "equivalent" Dockerfile, FROM node:22-slim plus RUN npm install -g @objectstack/cli@17.7.0. On that image's npm 10.9.9 the RUN exits 1 with edgesOut (measured). Its host recipe, npm install -g @objectstack/cli, is the same install. The file is outside this claim's surface.
  • Pre-existing scratch-cleanup gap. The probe's PREREQUISITE NOT MET exits call process.exit inside the try, so the scratch directory under the OS temp dir is not removed on those paths.

Generated by Claude Code

…d npm 11

The --resolve leg measures a governed vendor's export surface, but it
installed each scratch project with the runner's npm. npm 10's arborist
crashes in #loadPeerSet (`edgesOut` of null) on the upstream peer graph
vitest@4.1.12 introduced (reached through better-auth's optional vitest
peer), so the leg read PREREQUISITE NOT MET on every run without measuring
anything. The probe now fetches npm@11.21.0 into its scratch directory and
installs every vendor with it. --legacy-peer-deps was measured to drop
@better-auth/core's required peers (a false "does not load" finding), and
--omit=peer does not avoid the crash.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
npm 10's arborist crashes (`edgesOut` of null) installing a fresh blank
scaffold: optional `vitest` peers deeper in the closure resolve to
vitest@4.1.12, whose peer set contradicts itself. The template now routes
every vitest edge in the npm graph to the project's own declared range
with `"overrides": { "vitest": "$vitest" }`. npm 11 already dedupes the
tree to that vitest, and pnpm ignores the field.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
…nge, not $vitest

`"$vitest"` installs on npm 10 too, but npm then stops hoisting
@objectstack/metadata-core and nests 17 copies of it (measured on
10.9.9 and 11.21.0). The literal `~4.0.0` installs the same tree npm 11
installs without any override; npm refuses it loudly (EOVERRIDE) if it
ever differs from the direct dependency, which the template test pins.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/package.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/create-objectstack/src/templates/blank/package.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 1 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 9b799e6fb550923679f53780821268a7bac5fb09 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · PATCH ROUND · PR #23799 (draft, head c9134933e, Fixes #23788) · domain:devx seat 2 · session_016PVjjuVPte5XEJvaQx1E4t · 2026-10-16T03:44Z

Round 1 is accepted as far as it goes. One more leg of the same crash is open.

  • Round 1, checked by this seat, line by line:
    • scripts/check-vendor-export-contract.mjs: --resolve installs every probe with a pinned npm@11.21.0. It fetches that npm into the scratch dir with the runner's npm. A failed fetch, or an installer that is not exactly that version, is PREREQUISITE NOT MET. The self-test pins the version (exact, ≥11) and the whole argv. The measured exclusions are recorded in the constant's comment: --legacy-peer-deps is a false red on better-call; --omit=peer still crashes. CI on this head proves it: Validate Package Dependencies is success. Main's own schedule run 38843597390 (03:03Z) is red at that step.
    • The blank template's "overrides": { "vitest": "~4.0.0" } equals its devDependencies.vitest. The new consistency case pins the two equal, with an ablation. The changeset (create-objectstack patch) states the EOVERRIDE cost to users. The surface deviation the dev flagged is accepted: the cause is not a range, but a user-facing manifest is affected.
    • The trigger is named and bisected: vitest@4.1.12 (published 2026-10-15T14:51Z) peers @vitest/browser-webdriverio ^4.1.11, and the only match, 4.1.11, peers vitest: 4.1.11. npm --before gives 14:50 exit 0 and 14:52 the crash.
  • Still red: Scaffold with repo dist (job 116797775725). The registry install now passes, and the job reaches the docker build of the official runtime image. There, docker/Dockerfile:69 RUN npm install -g @objectstack/cli@${OS_CLI_VERSION} "pg@^8.0.0" "mysql2@^3.0.0" runs on node:22-slim's npm 10.9.9 and crashes with the same edgesOut.
    • A global install reads no project overrides, so round 1 cannot reach it.
    • This is the image docker-publish.yml ships, so the same line would break the release's image publish, and any user who builds docker/Dockerfile themselves.
  • Patch round: make that install run under npm 11 inside the image, with an exact pin, as the probe does.
  • Out of scope, to be filed by this seat:
    • the CLI-emitted os init / os create manifests (the same class, domain:cli);
    • existing projects' npm install @objectstack/plugin-auth / metadata-core on npm 10 (upstream root cause; the remedy and the optional vitest peer of @objectstack/metadata-core are for triage to route).

Generated by Claude Code

…inned npm 11

node:22-slim bundles npm 10.9.9, whose arborist crashes with `edgesOut`
on `npm install -g @objectstack/cli pg mysql2` since vitest@4.1.12 was
published, and a global install reads no `overrides`. The install RUN
now fetches npm@11.21.0 into /tmp with the image's npm, puts it first on
PATH for that RUN only, asserts its version, and deletes it in the same
layer, so the image keeps node:22-slim's npm and the installed set is
unchanged.

check-docs-image-tag anchors the driver install on `npm install -g`
after a `RUN` or `&&` leader. The installer fetch (no `-g`) is not an
anchor, and the self-test pins that the driver line is the one read.

Claude-Session: https://claude.ai/code/session_016PVjjuVPte5XEJvaQx1E4t
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · PR #23799 (draft, head d19cc5888, Fixes #23788) · domain:devx seat 2 · session_016PVjjuVPte5XEJvaQx1E4t · 2026-10-16T04:34Z

Checked on GitHub and against the sources, not on the report's say-so. Round 1 was accepted in 6207595239; this record covers the patch round and the whole PR.

  • Shape:
    • 6 files, +323 / −12, inside the revised claim's surface (6207588792).
    • Not governed: check-governed-merges --pr 23799, run by this seat, reads 0 of 6 paths.
    • The body's only closing keyword is Fixes #23788. Clause-②: no is at line start.
    • Nothing touches changeset-release/main, a workflow, .npmrc, pnpm-workspace.yaml or pnpm-lock.yaml.
  • Patch round, checked by this seat line by line:
    • docker/Dockerfile. The driver install keeps its exact package set: @objectstack/cli@${OS_CLI_VERSION}, pg@^8.0.0, mysql2@^3.0.0. It now runs in the same RUN after three steps:

      • fetch npm@11.21.0 into /tmp/os-installer-npm with the image's npm;
      • put it first on PATH for that RUN only;
      • assert test "$(npm --version)" = "11.21.0".

      The fetched installer is removed in the same layer, so the image keeps node:22-slim's npm. The comment beside the line names the measured exclusions (npm install -g npm@…, npx, --legacy-peer-deps).

    • scripts/check-docs-image-tag.mjs. The anchor is npm install -g after a RUN or && leader. The leader is dropped before the cut at the next &&, and the installer fetch (no -g) is not an anchor. Five new self-test cases cover the image's exact shape:

      • the driver line is the anchor, not the installer line;
      • npm@11.21.0 is not a driver;
      • green, 2 compared against 2;
      • a drifted range is refused (DRIVER-RANGE-MISMATCH);
      • a global npm install -g npm@… ahead of the drivers is refused (DRIVER-UNDOCUMENTED), never skipped into a green.

      docker/README.md is unchanged.

  • Measured by the dev (docker build, node:22-slim npm 10.9.9, Dockerfile bytes pinned by sha256):
    • Before: exit 1 edgesOut for OS_CLI_VERSION=17.7.0 and 18.0.0-next.2.
    • After: exit 0 for both.
    • The after image, against a pre-trigger control (npm --before set to 2026-10-15T14:50Z, before the vitest publish):
      • os --version, the image's npm 10.9.9, the global top level, and pg@8.23.1 / mysql2@3.24.6 are identical;
      • docker-publish.yml's smoke passes;
      • /tmp/os-installer-npm is absent.
    • linux/arm64 is NOT MEASURED. The mechanism is platform-independent JavaScript, and the publish job builds it.
  • Gates:
    • Local: 96 derived commands, 96/96 exit 0, --ran 0 NOT-MEASURED. They ran on a local, unpushed merge of origin/main + this head, because the branch tree read STALE against main's derivation inputs. That is the right tree for the reading.
    • CI on d19cc5888, read by this seat: 36 success, 7 roster skips, 0 failed. Scaffold with repo dist, Validate Package Dependencies and Check Changeset are all success.
  • PR body: the seat wrote the round-2 body from the dev's replacement in this act (18182 bytes, sha256 c08356156ec0…, read back identical), per os-dev.md:64.
  • Out of scope, routed by this seat:
  • Landing:
    • Once every check on the current head is green, the seat readies this PR and arms auto-merge through the relay, then confirms MERGED on origin/main.
    • It then dispatches the relay's release.yml refresh_version_pr stroke, so chore: version packages (next) #23481 regenerates carrying the fix.
    • A re-run of chore: version packages (next) #23481's bot-triggered checks after that is the maintainer's act.

Generated by Claude Code

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants