Repository navigation
[finding] create-objectstack: the scaffolded Dockerfile runs npm ci, but a pnpm-scaffolded project ships only pnpm-lock.yaml — docker build fails at the build stage #22150
Description
Activity
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsPath: the road — start: a scaffolded project deploys | 缺项 | P2
Triage: first grade,
bug·priority:p2·domain:cli·area:devpath·pm:queue(findingremoved). Direction: the scaffolded Dockerfile follows the project's package managerTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T04:58Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in
packages/create-objectstack/src/templates/blank/Dockerfileand the scaffolder's emission, plus the mirrored Dockerfile incontent/docs/deployment/self-hosting.mdx⇒domain:cli; rationale:create-objectstackis that lane's; the docs page is declared todomain:devxcross-lane. Read onmainec8f37c890.- Why p2:
docker buildof a fresh pnpm-scaffolded project fails before the build stage (measured). A newcomer's first deploy breaks. - Direction:
- emit a Dockerfile that matches the lockfile the scaffolder wrote (pnpm:
corepack+pnpm install --frozen-lockfile; npm:npm ci) - the docs page shows the same file
- emit a Dockerfile that matches the lockfile the scaffolder wrote (pnpm:
- Pins: a pnpm-scaffolded and an npm-scaffolded project each build their image; control:
os buildinside the image is unchanged. Clause-②: no. Patch changeset forcreate-objectstack.
- Why p2:
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 8
Session:session_01RWZbGvPFcRKvUqASZtunCU
Account:os-warren(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-22150-scaffold-dockerfile-package-manager
Worktree:objectstack-issue-22150
Domain:domain:cli
Seat:domain:cli#1
File surface, per triage6052631473, read onorigin/main6ed0c0f3:packages/create-objectstack/src/templates/blank/Dockerfile: its build stage (COPY package*.json ./+RUN npm ci,:15–:16).- The scaffolder's emission:
packages/create-objectstack/src/index.ts(the post-install Dockerfile step about:510, besidepinRuntimeImage) andruntime-image.tsor a new module beside it, together withdetect-package-manager.ts's answer. - Pins in
packages/create-objectstack/src/(pnpm-scaffolded and npm-scaffolded projects each get a build stage that matches their lockfile). - Cross-lane
domain:devx:content/docs/deployment/self-hosting.mdx, the mirrored Dockerfile block only. It is declared on the devx seat post. .changeset/*.md:create-objectstackpatch.- ⛔ The runtime stage and its image pin (create-objectstack: blank template Dockerfile uses
:latestwhile its own comment says to pin the CLI version #9017) are unchanged. ⛔ Nopackages/cli. (Stop on breach and explain in the report.)
Container & model:S,mode:subagent,model: default (opus).dispatch-gates --tierover the path gives no path-derived mandate.
Clause-②: no - A fix to what the scaffolder writes; no accepted input or published shape widens.
Responsibility:platform code: create-objectstack's blank template Dockerfile hard-codes COPY package*.json + npm ci (templates/blank/Dockerfile :15-:16) while the scaffolder installs with the detected package manager|none: no test builds the scaffolded Dockerfile's build stage against the lockfile the scaffolder wrote|every newcomer who scaffolds with pnpm on PATH and follows the README's Ship it section (measured once in the card)
Thread-read: 6052631473
Serial constraints cleared: read 2026-10-08T05:14Z: - Open PRs (11 read, each file list read by
filename): PR chore: version packages #21988 (Version Packages) touchescontent/docs/deployment/self-hosting.mdx(the runtime image tag lines,:75and:93) andcreate-objectstack'sCHANGELOG.md/package.json. That is release machinery in a different region of the page; ordinary concurrency. - In-flight claims: spec(v18): move PROTOCOL_VERSION 17 → 18 in an ordinary pull request with full CI: regenerate spec-changes.json and the 17 → 18 upgrade-guide section, rewrite the in-repo ^17 handshakes, and give the lockstep test one pre-mode exception (#22085 Q1) #22130 (
6051334172) writes onecreate-objectstackfile, the template'sobjectstack.config.ts^17handshake. That is not this claim's files. - This seat's siblings in this round (finding(runtime): through the @objectstack/hono catch-all, PUT /meta/:type/:name ignores If-Match, If-None-Match and ?mode=draft — a stale token writes (200, not 409) and a draft save lands ACTIVE #22141, [finding] client:
envelope-caller-census.test.tswalks.cache/, sopnpm testgoes red onmainright after the documentedpnpm objectui:build— the objectui checkout it leaves behind is counted as a 29th SDK call site #22153) touch nothing here.
domain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T05:14Zobjectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22150,
"status": "done",
"branch": "claude/issue-22150-scaffold-dockerfile-package-manager",
"pr": "#22217",
"session": "session_01RWZbGvPFcRKvUqASZtunCU",
"premise_still_valid": true,
"summary": "Reproduced H1 on a real pnpm scaffold: the old stage copies only package.json andnpm ciexits 1 with EUSAGE. The template's build stage now copiespackage.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json*and installs from whichever lockfile is present. pnpm-lock.yaml getscorepack pnpm@10 install --frozen-lockfile; package-lock.json getsnpm ci; with neither, the build fails loudly with the remedy. Measured from clean copies against the registry: pnpm- and npm-scaffolded projects both install and runos buildgreen. The self-hosting docs block shows the same stage, and a new hermetic pin in create-objectstack runs the stage's real install, with an ablation on record. Route differs from the PM's H2 lean (no scaffold-time emission); the measured reasons are under deviations, and the ruling's outcome is unchanged.",
"tests": "HEAD cc944d6. Package:pnpm --filter create-objectstack testexit 0, 'Test Files 17 passed (17) / Tests 254 passed (254)';typecheckexit 0, andtsc --listFilesincludes all 17 src/*.test.ts (the new one among them). The dependency closure (pnpm --filter 'create-objectstack^...' build, i.e. spec) was built first, exit 0. The package has no integration tier. New pin src/dockerfile-build-stage.test.ts: 5 passed. Its legs: a pnpm-installed fixture, an npm-installed fixture, and a no-lockfile project, each running the stage's COPY+RUN from a clean dir under /bin/sh, offline, with a stub corepack that runs the repo-pinned pnpm and refuses a major mismatch. Two more cases: the docs build stage equals the template's, and the Dockerfile pnpm major equals ci.yml's. ABLATION: scripts/ablation-replace.mjs, inside one lock hold on cc944d6; no build/dist on the path, since the test reads src/templates through copyDir. It put backCOPY package*.json ./+RUN npm ci: anchor 1 to 0, blob ce699d28904a to 04316706900a, on-disk new-line count 0 and old-line count 1. Result 'Tests 3 failed | 2 passed (5)'. Red: the pnpm leg (npm error code EUSAGE), the docs pin and the major pin. Green: the npm and no-lockfile legs. Restore: blob back to ce699d28904a, equal to HEAD;git diff HEADempty; porcelain clean. REAL-REGISTRY MEASUREMENT, one-off, with a scratch harness that emulates each stage from a clean copy (BuildKit glob COPY, /bin/sh=dash, fresh HOME/COREPACK_HOME/store), with stage text taken from the committed file. pnpm-scaffolded project (lockfile written by pnpm 10.28.0): the old stage exits 1 with EUSAGE; the new stage installs with pnpm 10.34.6 ('Lockfile is up to date'), exit 0, thennpx os buildexit 0 and dist/objectstack.json is written. npm-scaffolded project: the new stage reports 'added 524 packages', exit 0, and os build exit 0; the old stage also exits 0. No lockfile: exit 1 with the remedy line. Corepack 0.34.0 / Node 22.22.0: unpinned, it resolves pnpm 12.10.1 and fails with MODULE_NOT_FOUND on bin/pnpm.cjs; COREPACK_DEFAULT_TO_LATEST=0 gives 10.13.1, below engines.pnpm >=10.15; pnpm@10 gives 10.34.6 and works; pnpm@11.28.2 also runs. Without pnpm-workspace.yaml, the frozen install exits 0 but prints 'Ignored build scripts: better-sqlite3@13.0.3, esbuild@0.28.2'.",
"mcp_calls": "0 — no MCP GitHub tool called, read or write",
"api_writes": "3, all as one repository_dispatch each to the fleet-write relay (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls, draft #22217; body read back as 11483 bytes sent and stored, identical. (2) label-write --assign os-warren: POST /repos//issues/22217/assignees; read back as assignee os-warren. (3) post-stamped: POST /repos//issues/22150/comments, this report. Zero label writes: the order names no label, and skip-changeset does not apply because a patch changeset ships.git pushx5 is not REST. The card's assignee was not written.",
"files_changed": [
".changeset/22150-scaffold-dockerfile-lockfile.md (+14/-0)",
"content/docs/deployment/self-hosting.mdx (+8/-2, the Dockerfile block build stage only; the image-tag lines are untouched)",
"packages/create-objectstack/src/dockerfile-build-stage.test.ts (+329/-0)",
"packages/create-objectstack/src/templates/blank/Dockerfile (+8/-2, the build stage only; the runtime stage is byte-identical)"
],
"gates": "HEAD cc944d6, clean tree.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderives 93 commands, identical to the order's list minus thepnpm lintthe order adds. Ran all 93 pluspnpm lint: 94/94 exit 0.dispatch-gates --ran: '93 derived, 93 run, 0 NOT-MEASURED, 0 UNRUN'. Five gates exited 3 (PREREQUISITE NOT MET) on an earlier pass before builds: lint check:doc-formula-expressions, lint check:doc-security-posture, check:docs-transcript-drift, check:dual-build-cjs-loads, check:lean-entry-closure. They exit 0 in the final union, afterturbo run build --filter='!@objectstack/docs' --concurrency=2(72/72).pnpm lint(eslint . --no-inline-config) exit 0, no findings, about 108s on a shared box. Narrowed lint was also run: eslint lints 1 of the 4 changed paths (the other three: 'File ignored because no matching configuration was supplied'), 0 errors and 0 warnings, and eslint.config.mjs never enables type-aware linting. Quoted verdicts: check:nul-bytes 'OK (scanned 10161 text file(s) ... no raw ASCII control bytes)'; check:cross-package-test-inputs 'OK: 30 package(s) read outside themselves, all declared' (the new test reads content/docs under the existing$TURBO_ROOT$ /content/** input); check-empty-changeset '1 declaring changeset(s) added'. CI convergence: in_progress, not awaited.",
"line_budget": "n/a. No skills/** and no governed ledger touched. Diff: 4 files, +359/-4, under the 5000-line human-merge threshold.",
"deviations": [
"Route (PM reading H2, a suggested route): I did NOT emit the build stage post-install from the detected package manager. Measured: scaffold-e2e.yml scaffold-local scaffolds with --skip-install on a runner whose pnpm is a Corepack shim, then runsnpm installanddocker builds the scaffolded Dockerfile. A stage emitted from the detected manager would COPY a pnpm-lock.yaml that does not exist there. Emitting only after a successful install instead leaves --skip-install pnpm users onnpm ci. And outside a project, the manager detected on such a runner depends on Corepack's LastKnownGood or latest. So I shipped one stage that reads the lockfile at build time, which is right on every path. The ruling's outcome holds: pnpm gets Corepack + frozen install, npm gets npm ci, and the docs show the same file. index.ts, runtime-image.ts and detect-package-manager.ts, named on the claim's surface, are untouched; the surface used is a subset.",
"The ruling's 'corepack + pnpm install --frozen-lockfile' is spelledcorepack pnpm@10 install --frozen-lockfile. An unpinned Corepack resolves pnpm 12, which Corepack 0.34 cannot run, and the bundled default is below engines.pnpm. Major 10 is the major the template's ci.yml pins.",
"One test file beyond the triage pins also holds two equalities: the docs block's build stage equals the template's, and the Dockerfile's pnpm major equals ci.yml's. No turbo.json change: content/** is already a declared input of create-objectstack#test.",
"The first ablation run also reddened the npm leg. The cause was an over-specific assertion in my own test, which required pnpm-workspace.yaml in the npm copy set. Commit cc944d6 fixed it, and the ablation was re-run on that head.",
"Attribution: commits carry the order's model-free trailer pair. The harness reminder's model-named trailer and its two-line PR footer were not used (os-dev.md and AGENTS.md take precedence). The PR body ends with the AGENTS session-URL footer block only.",
"Process: one background PID was read back withpgrep -f. It was confirmed withps -pto be the gate runner beforetail --pidwaited on it; nothing was killed by name. The first lock attempt for the new test returned 99 (NOT MEASURED) behind another agent's long hold; it was re-acquired on the kept slot."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted in the PR's Acceptance notes, not filed. CI's only docker build of a scaffold (scaffold-e2e.yml scaffold-local) installs withnpm install, so it exercises only thenpm cibranch. The pnpm branch is covered by the hermetic pin and the one-off real-registry measurement. Coverage note, not a defect; scaffold-e2e.yml is outside this card's surface. Dedupe words: scaffold-e2e docker pnpm branch, scaffolded Dockerfile pnpm docker build CI, create-objectstack docker leg npm only"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsACCEPT — PR #22217 at
cc944d6b, pending CI and the seat's self-testdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T06:43ZChecked on GitHub and in the diff, not from the report:
- Shape:
- draft, base
main; - line 1
Fixes #22150, line 2Clause-②: no; - assignee
os-warren; - 4 files, +359 / −4;
- the changeset is
create-objectstackpatch.
- draft, base
- The build stage (
templates/blank/Dockerfile):- It copies
package.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json*. - It installs from the lockfile present:
corepack pnpm@10 install --frozen-lockfile, ornpm ci. - With neither lockfile it exits 1 with the remedy.
- The runtime stage and its image pin are byte-identical.
- The template's
.dockerignoredoes not exclude any of the copied lockfiles.
- It copies
pnpm@10is the major the template's.github/workflows/ci.ymlpins (pnpm/action-setup,version: 10), and a pin holds the two in step.- The docs block in
content/docs/deployment/self-hosting.mdxshows the same build stage, and a pin holds that too. Only the Dockerfile block moved; the image-tag lines the release PR chore: version packages #21988 changes are untouched. The cross-lane edit is declared todomain:devx(6052853912,6052860601). - The route differs from the PM's H2 lean, and is accepted. H2 was "optional; measurement wins". The dev measured that the scaffold-time emission breaks
scaffold-e2e.yml's--skip-installscaffold, which then runsnpm installanddocker builds the file. It would also leave--skip-installpnpm users onnpm ci. One stage that reads the lockfile at build time is right on every path, and it delivers the ruling's outcome (pnpm gets Corepack plus a frozen install, npm getsnpm ci, and the docs show the same file). - The changeset's sentences:
- "installs from the lockfile": true of the diff.
- "With neither, the build stops and says to install once and commit the lockfile": true; the echo says "No lockfile: run pnpm install or npm install, then commit it."
- "pinned to major 10, the major the template's ci.yml installs with": true.
- "an unpinned Corepack takes the registry's newest pnpm, which the Corepack bundled with Node 22 could not run when this was measured": the dev's measurement (Corepack 0.34.0 resolved pnpm 12.10.1 and failed), worded as a measurement.
- The migration sentence for already-scaffolded projects is true.
- Pins (
dockerfile-build-stage.test.ts, 5 cases, hermetic):- a pnpm-installed leg, an npm-installed leg and a no-lockfile leg, each running the stage's COPY and RUN from a clean directory under
/bin/sh; - docs block equals template;
- the Dockerfile's pnpm major equals
ci.yml's. - Ablation (the old
npm cistage put back): the pnpm leg turns red withEUSAGE, the npm and no-lockfile legs stay green, and the tree was restored to a blob equal to HEAD.
- a pnpm-installed leg, an npm-installed leg and a no-lockfile leg, each running the stage's COPY and RUN from a clean directory under
- The dev's real-registry measurement (a one-off harness, from clean copies):
- a pnpm scaffold installs and
os buildwritesdist/objectstack.json; - an npm scaffold the same;
- the old stage fails
EUSAGEon pnpm. - ⛔ No real
docker buildran (no daemon here); the stage commands were run directly, as the card measured too.
- a pnpm scaffold installs and
Kept as Acceptance notes, no carrier:
- CI's only scaffold
docker build(scaffold-e2e.yml) installs withnpm install, so it exercises thenpm cibranch only. The pnpm branch is held by the hermetic pin.
Evidence (the dev's, at
cc944d6b):create-objectstacktests: 17 files and 254 tests;- typecheck exits 0;
dispatch-gates --ran: 93 of 93;pnpm lintexits 0.
The seat's own: the new test adds
mkdtempSyncsites (standing lesson 1).node scripts/pm/dispatch-gates.mjs --self-testatcc944d6bis running and is read before landing.CI on
cc944d6b, read 2026-10-08T06:43Z: 23 success · 3 skipped · 8 in progress · 0 red. ⛔ Not green.Clause-②: no: no contract review is owed (noClause-②: yes, nopackages/spec, no governed surface). Landing follows CI green and the self-test.- Shape:
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsLanded: PR #22217 →
5f98778dce, a single-parent queue squashdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T08:21Z- Landing shape:
5f98778dcehas one parent.- It is an ancestor of
origin/main; the pre-merge headcc944d6bis not. - It entered the merge queue 2026-10-08T07:11:39Z and merged 2026-10-08T08:20:41Z on that first entry.
Fixes #22150closed this card as completed.
- Content on
origin/main:- the blank template's
Dockerfilecopiespackage.json pnpm-lock.yaml* pnpm-workspace.yaml* package-lock.json* ./and installs from whichever lockfile is present:corepack pnpm@10 install --frozen-lockfile, elsenpm ci, else a refusal naming the missing lockfile (:19–:21); content/docs/deployment/self-hosting.mdxshows the same build stage (:145–:146);packages/create-objectstack/src/dockerfile-build-stage.test.tsand.changeset/22150-scaffold-dockerfile-lockfile.md(create-objectstackpatch) are present.
- the blank template's
- Review of record: ACCEPT
6054112427atcc944d6b, in one round.Clause-②: no; no contract review was owed.- CI on the head was green (37 runs) before the PR was armed.
- The seat's
dispatch-gates --self-testpassed (1976 cases) for the new temp-directory site. - The cross-lane docs edit was declared to
domain:devx(6052853912,6052860601), and no objection came.
- Delivered: a project scaffolded and installed with pnpm builds its image from its own
pnpm-lock.yaml, instead ofnpm cifailing for want of apackage-lock.json. An npm project builds exactly as before. A project with no lockfile is refused with a sentence that says why, rather than an unpinned install.
- Landing shape:
- added a commit that references this issue
on Oct 9, 2026
Filing gate: ① product defect with a named landing spot and a reproduction (
findingclass (a): reproducible defect).reach: public entry measured once — the Dockerfile's own build stage (
COPY package*.json ./+RUN npm ci) run against a freshnpm create objectstack@latestproject exits 1 withEUSAGE, beforeos buildever runs.Reader:
domain:cliseat (create-objectstack owns the template); fix lands inpackages/create-objectstack/src/templates/blank/Dockerfileor the scaffolder's package-manager-aware emission, plus the matching Dockerfile incontent/docs/deployment/self-hosting.mdx.Dedup:
search_issues"scaffolded Dockerfile npm ci fails pnpm-lock.yaml package-lock.json docker build create-objectstack" (open + closed) → 0 cards on this defect; nearest are the registry-canary cards #20382 / #19510 / #16500 (closed, about the npm install itself, not the Dockerfile).Filed on the maintainer's instruction in this session: 「设想你是一个新人,第一次打开 github objectstack 项目主页,了解本项目,并按照文档指引执行完整的试用流程,并对阅读文档和试用过程中遇到的问题立 issue」.
Summary
npm create objectstack@latest my-appdetects pnpm when it is on PATH, installs with pnpm, and writespnpm-lock.yaml(nopackage-lock.json). TheDockerfileit writes into the same project hard-codes the build stage as:npm cirefuses to run withoutpackage-lock.json/npm-shrinkwrap.json, so the first command the README's Ship it section tells a new user to run (docker build -t my-app . && docker compose up -d) fails before the artifact is compiled. The README states the scaffolded project is "container-ready", andcontent/docs/deployment/self-hosting.mdxrepeats the samenpm ciDockerfile.The scaffolder is already package-manager aware elsewhere: the closing summary prints
pnpm run dev, and the generated.github/workflows/ci.ymlusespnpm/action-setup+pnpm install --frozen-lockfileand comments thatpnpm-lock.yaml"has to be committed". Only the Dockerfile still assumes npm.Reproduction
Environment:
create-objectstack@17.7.0, Node v22.22.0, pnpm 10.31.0 on PATH, npm 10.9.4.Running the build stage exactly as the Dockerfile does (copy the tree without
node_modules, runnpm ci):Exit code 1, so
docker buildstops atRUN npm ci. (This session had no Docker daemon, so the stage was reproduced by running the same two commands in a copy of the project; the failure does not depend on Docker.)Expected
docker build -t my-app .on a freshly scaffolded project succeeds with whichever package manager the scaffolder actually used — or the scaffolder writes a Dockerfile that matches the lockfile it just produced.Actual
The build stage fails with
EUSAGEfor every project scaffolded on a machine where pnpm is present.Suggested direction
Either keeps the Dockerfile and the lockfile in agreement:
detect-package-manager.ts): for pnpm,COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./+corepack enable && pnpm install --frozen-lockfile; for npm, the currentnpm ci.COPY package.json ./+npm install --no-audit --no-fund), accepting the loss of lockfile pinning inside the image.The self-hosting docs page should follow whichever shape lands.
Generated by Claude Code