You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
create-objectstack: scaffolding into a non-empty current directory overwrites the user's README.md and package.json with exit 0 and no warning, while a named non-empty directory is refused #29134
立卡门类别: ① 产品缺陷 — 具名落点 (packages/create-objectstack/src/index.ts, the !isCurrentDir-gated emptiness refusal; packages/create-objectstack/src/template-copy.ts copyDir) + 复现.
reach: 公开入口实测一次错误结果 — create-objectstack with no name in a directory holding README.md and package.json exits 0 and replaces both (create-objectstack from 7fa9873, reproduced with no name and with "").
业务理由: a newcomer who runs the scaffolder inside an existing folder (the help says no name means "the current directory") loses their own README and package.json without a prompt, a backup or a warning. The same scaffolder refuses that exact situation when the folder is named ("Directory already exists and is not empty", exit 1), so the user's files are protected or destroyed depending on how they typed the command. Not doing it: the first-run path can silently delete user work.
动手的读者: the domain:server seat (create-objectstack); triage first touch confirms the lane, then os-dev.
查重: create-objectstack current directory overwrite → 38 hits, create-objectstack "current directory" → 20 hits, scaffold "already had contents" → 0 hits (all incl. closed): none matches. Not among the wave-4 cards (#28954, #28992 are other scaffold defects).
QA-source: #29124 · cli.scaffold-first-run · observed beside the clauses
What happens.index.ts refuses a non-empty target only when !isCurrentDir; its own comment says the current directory "is the one path that can land in a directory that already had contents" and only adjusts the closing summary for it. The template copy then writes every template file with fs.copyFileSync and no existence check, so same-named user files are replaced. The only signal is the summary heading "(the directory already had contents; this lists all of it)".
Reproduction:
mkdir d && cd d && echo "MY NOTES" > README.md && echo '{"name":"mine","version":"9.9.9"}' > package.json
node ‹checkout›/packages/create-objectstack/bin/create-objectstack.js "" -t blank --skip-install --skip-skills (the same with no name argument).
Exit 0. README.md is the template's; package.json reads the template's name and "version": "0.1.0". "MY NOTES" is gone.
Control: cd .. && node ‹checkout›/packages/create-objectstack/bin/create-objectstack.js d -t blank --skip-install → exit 1 "Directory already exists and is not empty", nothing written.
Expected: the current-directory path refuses to overwrite existing files (or refuses a non-empty directory as the named path does), naming the files it would replace.
Claim: PM loop round 1 · 2026-11-02T09:50Z
Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); dev = card thread session_01ADHkmQNsRpYUsG5efyi5Ur
Account: os-steve
Branch: claude/project-thread-fveq44 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own
Domain: domain:server
Seat: domain:server#1
File surface: the target-directory check in packages/create-objectstack/src/index.ts (the !isCurrentDir-gated emptiness refusal) and, if needed, copyDir in packages/create-objectstack/src/template-copy.ts; tests; a create-objectstack changeset. ⛔ No packages/spec/**, no new CLI flag, no new gate. (stop on breach; explain in the report)
Container & model: S, mode:thread, model as the thread was opened, effort: medium
Clause-②: no (narrowing) (expected: a no-name scaffold into a current directory whose files the template would overwrite, exit 0 on main, now refuses with nothing written; the dev measures)
Metadata-protocol: none (expected)
Responsibility: the code is the scaffolder of create-objectstack · the platform path is a first run in an existing folder that silently replaces the user's README.md and package.json · reached by every newcomer who runs the scaffolder with no name, today
Thread-read: 6572501857
Premise: holds (seat read on main; the dev re-measures on base first)
Serial constraints cleared: PR #29103 (#28992, seat 2, same index.ts) merged at 2026-11-02T09:37Z; this seat's #29091 (templates/AGENTS.md) merged. No other open PR touches create-objectstack/src.
Orders: the seat's standard order plus server-seat1/29134-delta.md in the project's shared folder.
{
"issue": 29134,
"status": "done",
"branch": "claude/project-thread-fveq44",
"pr": "https://github.com/objectstack-ai/objectstack/pull/29267",
"session": "session_01ADHkmQNsRpYUsG5efyi5Ur",
"premise_still_valid": true,
"summary": "Premise re-measured on base 49aba82b: a no-name and an empty-name run in a current directory holding README.md + package.json exited 0 and replaced both files. Added read-only findTemplateCollisions (template-copy.ts, same alias walk as copyDir) and, before the first write in index.ts, a refusal (exit 1) that names every template path that already exists (or a non-directory standing where the template needs a directory) and writes nothing. Collision-scoped shape as the delta prefers: a current dir holding only files the template does not write still scaffolds and keeps them; the named non-empty refusal and the empty-dir path are unchanged.",
"tests": "head 791e47c3: new src/scaffold-current-dir-collision.test.ts 8/8 (no-name and empty-name collision refusals with whole-tree byte snapshot, non-colliding .git/HEAD + notes.txt scaffolds untouched, empty current dir, named non-empty old refusal, 3 findTemplateCollisions unit cases). Full package under os-verify-lock (closure build + build + test + typecheck): VERDICT command-exit 0, Test Files 23 passed (23), Tests 356 passed (356); tsc --listFiles includes the new test (1). Ablation via ablation-replace.mjs on index.ts: anchor 'if (collisions.length > 0) {' x1->x0, replacement x0->x1, blob c402bf41f8e7->ee6ac763dfa4; red: 'refuses with no name' and 'refuses with an empty name' failed, 'AssertionError: expected +0 to be 1', Tests 2 failed | 6 passed (8); restored blob == HEAD c402bf41f8e7, git diff HEAD empty. Suite runs src via tsx/vitest, no dist in the resolution path, so no rebuild/dist-preflight leg.",
"gates": "dispatch-gates --commands at 791e47c3: 57 derived; all 57 exit 0 (3 first exited 3 on prerequisites and re-ran green: check:type-check-debt and check:dual-build-cjs-loads after the dogfood closure build, check-plugin-teardown-shape --self-test after fetching fixture commit 621a4876). --ran reconciliation with exit codes: 57 derived, 57 run, 0 NOT-MEASURED (derived zero). pnpm lint family: root eslint over packages/create-objectstack --no-inline-config exit 0. PR check-runs: in_progress at report time (not awaited).",
"dogfood_battery": "791e47c3 · os-verify-lock VERDICT command-exit 0 (held 34m58s) · Test Files 388 passed | 3 skipped (391) · Tests 3208 passed | 28 skipped (3236) · no failures",
"mcp_calls": "1 — mcp__github__pull_request_read get (#29267 read-back); no MCP writes",
"api_writes": "2 — fleet-write relay pr_create (PR #29267, 6643 bytes sent = stored, assignees none); post-stamped issue comment on #29134 (this report)",
"open_questions": [
{ "question": "Should create-objectstack offer an explicit opt-in to scaffold over existing files (a --force flag)?", "options": ["A no flag: the refusal names the files, the user moves them aside or scaffolds into a named directory", "B add --force to overwrite colliding files on purpose (new public CLI flag, needs maintainer approval)"], "recommendation": "A, because the card asked for protection, the remedy is one mv away, and a new flag is a public surface the delta keeps out of this card" },
{ "question": "The refusal is a new sentence ('Directory is not empty and the template would overwrite N existing files: DIR'), reusing the existing 'Directory ... not empty' family; no error code exists in this CLI. OK to land as worded?", "options": ["A land as worded", "B reword to extend the old sentence verbatim"], "recommendation": "A, because it names the collision count and keeps the existing family's lead" }
],
"out_of_scope_findings": [],
"files_changed": [
"packages/create-objectstack/src/index.ts",
"packages/create-objectstack/src/template-copy.ts",
"packages/create-objectstack/src/scaffold-current-dir-collision.test.ts",
".changeset/29134-current-dir-overwrite-refusal.md"
],
"line_budget": "+249 / -6 across 4 files (255 changed lines, under the 3000 human-merge threshold); effort S",
"deviations": [
"PR body line 1-2 is the harness project-attribution block (marker comment + Requested-by line); Fixes #29134 is line 3, per os-dev's harness-attribution exception, not line 1 as the standard order says.",
"Session-context asks to set the PR assignee/reviewer via MCP after creation; skipped because the standard order forbids MCP writes and os-dev forbids PR assignees.",
"Dogfood ran in the background (nohup, os-verify-lock) while the derived gates ran unlocked; three gates first exited 3 on prerequisites during that window and were re-run green after it."
]
}
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: cli.scaffold-first-run
Triage:
domain:server· p2 ·pm:queue· 2026-11-02T08:24Z⛔ Not a claim.
os validatewarns about (sanitizeNamespace caps at 20, the #28473 rule warns above 13) #28992 are other scaffold defects, not duplicates.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Claim: PM loop round 1 · 2026-11-02T09:50Z
Session:
session_01ADHkmQjPiXgNoSaEpqeGvY(seat); dev = card threadsession_01ADHkmQNsRpYUsG5efyi5UrAccount:
os-steveBranch:
claude/project-thread-fveq44(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own
Domain:
domain:serverSeat:
domain:server#1File surface: the target-directory check in
packages/create-objectstack/src/index.ts(the!isCurrentDir-gated emptiness refusal) and, if needed,copyDirinpackages/create-objectstack/src/template-copy.ts; tests; acreate-objectstackchangeset. ⛔ Nopackages/spec/**, no new CLI flag, no new gate. (stop on breach; explain in the report)Container & model:
S,mode:thread, model as the thread was opened,effort: mediumClause-②: no (narrowing) (expected: a no-name scaffold into a current directory whose files the template would overwrite, exit 0 on
main, now refuses with nothing written; the dev measures)Metadata-protocol: none (expected)
Responsibility: the code is the scaffolder of
create-objectstack· the platform path is a first run in an existing folder that silently replaces the user's README.md and package.json · reached by every newcomer who runs the scaffolder with no name, todayThread-read: 6572501857
Premise: holds (seat read on
main; the dev re-measures on base first)Serial constraints cleared: PR #29103 (#28992, seat 2, same
index.ts) merged at 2026-11-02T09:37Z; this seat's #29091 (templates/AGENTS.md) merged. No other open PR touchescreate-objectstack/src.Orders: the seat's standard order plus server-seat1/29134-delta.md in the project's shared folder.
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQNsRpYUsG5efyi5Ur
os-dev-report
{ "issue": 29134, "status": "done", "branch": "claude/project-thread-fveq44", "pr": "https://github.com/objectstack-ai/objectstack/pull/29267", "session": "session_01ADHkmQNsRpYUsG5efyi5Ur", "premise_still_valid": true, "summary": "Premise re-measured on base 49aba82b: a no-name and an empty-name run in a current directory holding README.md + package.json exited 0 and replaced both files. Added read-only findTemplateCollisions (template-copy.ts, same alias walk as copyDir) and, before the first write in index.ts, a refusal (exit 1) that names every template path that already exists (or a non-directory standing where the template needs a directory) and writes nothing. Collision-scoped shape as the delta prefers: a current dir holding only files the template does not write still scaffolds and keeps them; the named non-empty refusal and the empty-dir path are unchanged.", "tests": "head 791e47c3: new src/scaffold-current-dir-collision.test.ts 8/8 (no-name and empty-name collision refusals with whole-tree byte snapshot, non-colliding .git/HEAD + notes.txt scaffolds untouched, empty current dir, named non-empty old refusal, 3 findTemplateCollisions unit cases). Full package under os-verify-lock (closure build + build + test + typecheck): VERDICT command-exit 0, Test Files 23 passed (23), Tests 356 passed (356); tsc --listFiles includes the new test (1). Ablation via ablation-replace.mjs on index.ts: anchor 'if (collisions.length > 0) {' x1->x0, replacement x0->x1, blob c402bf41f8e7->ee6ac763dfa4; red: 'refuses with no name' and 'refuses with an empty name' failed, 'AssertionError: expected +0 to be 1', Tests 2 failed | 6 passed (8); restored blob == HEAD c402bf41f8e7, git diff HEAD empty. Suite runs src via tsx/vitest, no dist in the resolution path, so no rebuild/dist-preflight leg.", "gates": "dispatch-gates --commands at 791e47c3: 57 derived; all 57 exit 0 (3 first exited 3 on prerequisites and re-ran green: check:type-check-debt and check:dual-build-cjs-loads after the dogfood closure build, check-plugin-teardown-shape --self-test after fetching fixture commit 621a4876). --ran reconciliation with exit codes: 57 derived, 57 run, 0 NOT-MEASURED (derived zero). pnpm lint family: root eslint over packages/create-objectstack --no-inline-config exit 0. PR check-runs: in_progress at report time (not awaited).", "dogfood_battery": "791e47c3 · os-verify-lock VERDICT command-exit 0 (held 34m58s) · Test Files 388 passed | 3 skipped (391) · Tests 3208 passed | 28 skipped (3236) · no failures", "mcp_calls": "1 — mcp__github__pull_request_read get (#29267 read-back); no MCP writes", "api_writes": "2 — fleet-write relay pr_create (PR #29267, 6643 bytes sent = stored, assignees none); post-stamped issue comment on #29134 (this report)", "open_questions": [ { "question": "Should create-objectstack offer an explicit opt-in to scaffold over existing files (a --force flag)?", "options": ["A no flag: the refusal names the files, the user moves them aside or scaffolds into a named directory", "B add --force to overwrite colliding files on purpose (new public CLI flag, needs maintainer approval)"], "recommendation": "A, because the card asked for protection, the remedy is one mv away, and a new flag is a public surface the delta keeps out of this card" }, { "question": "The refusal is a new sentence ('Directory is not empty and the template would overwrite N existing files: DIR'), reusing the existing 'Directory ... not empty' family; no error code exists in this CLI. OK to land as worded?", "options": ["A land as worded", "B reword to extend the old sentence verbatim"], "recommendation": "A, because it names the collision count and keeps the existing family's lead" } ], "out_of_scope_findings": [], "files_changed": [ "packages/create-objectstack/src/index.ts", "packages/create-objectstack/src/template-copy.ts", "packages/create-objectstack/src/scaffold-current-dir-collision.test.ts", ".changeset/29134-current-dir-overwrite-refusal.md" ], "line_budget": "+249 / -6 across 4 files (255 changed lines, under the 3000 human-merge threshold); effort S", "deviations": [ "PR body line 1-2 is the harness project-attribution block (marker comment + Requested-by line); Fixes #29134 is line 3, per os-dev's harness-attribution exception, not line 1 as the standard order says.", "Session-context asks to set the PR assignee/reviewer via MCP after creation; skipped because the standard order forbids MCP writes and os-dev forbids PR assignees.", "Dogfood ran in the background (nohup, os-verify-lock) while the derived gates ran unlocked; three gates first exited 3 on prerequisites during that window and were re-run green after it." ] }Stamped 2026-11-02T10:44Z.
objectstack-fleet commentedon Nov 2, 2026
domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY
Landed · 2026-11-02T11:52Z
ad224b79f7, through the merge queuecompletedbyFixes #29134pm:dispatchedis removed. Seat record: claim 6574098591, contract review PASS 6575443702, seat ACCEPT 6575476596.