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 accepts a project name that is not a legal npm package name and writes it to package.json, where npm publish later refuses it #26642
reach: a newcomer who passes a display-style name such as "My App!" or "MyApp" to create-objectstack gets a project whose package.json name npm rejects. Nothing on the first-run path says so: validate and build pass. The first refusal comes at npm publish (or any registry step), far from the cause.
Run npx -y create-objectstack@latest "My App!" --skip-install; echo $?. It exits 0 and prints Environment: My App!, Namespace: my_app, ✓ Environment created!.
My App!/package.json has "name": "My App!", and objectstack.manifest.json has "name": "My App!".
A third run with the install (create-objectstack "My App!" --skip-skills) then runs npm run validate (exit 0) and npm run build (exit 0). npm publish --dry-run fails with npm error Invalid name: "My App!".
Expected: a name that is not a legal npm package name (uppercase, space, !) is refused at the prompt with the rule stated, or normalised (for example my-app) with a notice.
Actual: it is written verbatim.
Mechanism
packages/create-objectstack/src/index.ts: projectName = name || path.basename(cwd), then rewriteProjectIdentity sets pkg.name = projectName. No validation runs.
sanitizeNamespace normalises the ObjectStack namespace only. Its own comment says it converts "an npm package name", which assumes an input nothing checked.
Not in scope
The scaffolder still exits 0 after its own dependency install fails. That is the ruled behaviour (#23865 / PR #23966: a failed install is non-fatal, and "Next steps" names the install) and is not part of this card.
p2:create-objectstack accepts a project name that is not a legal npm package name and writes it to package.json, so the first refusal comes at npm publish. Validate the name up front and suggest the slug. ⛔ Never rewrite it silently.
Claim: PM loop round 1
Session: session_01QSeKYmQXM3rHJcPnrEPk4B
Account: os-justin
Branch: claude/issue-26642-npm-name-check
Worktree: objectstack-issue-26642
Domain: domain:server
Seat: domain:server#3
File surface: packages/create-objectstack/src/** and its tests; one patch changeset (stop on breach; explain in the report). ⛔ No packages/spec/** (lane red line).
Container & model: M (S-sized, not mechanical: the refusal wording and the slug suggestion are design judgment), mode:subagent, model: opus (dispatch-gates --tier: "no path-derived mandate … default opus"), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: packages/create-objectstack/src/index.ts writes projectName into package.json unvalidated | none: sanitizeNamespace normalises only the ObjectStack namespace | a newcomer passing a display-style name to create-objectstack (measured on 18.2.0 in the card)
Thread-read: 6441418010
Premise: holds (seat re-check on origin/main74db43ef: index.ts:502const projectName = name || path.basename(cwd) and :187pkg.name = projectName, no validation between)
Serial constraints cleared: none (no open pm:dispatched claim declares packages/create-objectstack/**)
{
"issue": 26642,
"status": "done",
"branch": "claude/issue-26642-npm-name-check",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26796",
"session": "session_01QSeKYmQXM3rHJcPnrEPk4B",
"premise_still_valid": true,
"summary": "Premise held at origin/main a485b5a3: index.ts resolved projectName = name || path.basename(cwd) and wrote it to package.json and objectstack.manifest.json with no check in between, and create-objectstack has no interactive prompt path. A new module, packages/create-objectstack/src/package-name.ts, checks the resolved name (argument or current-directory basename) with validate-npm-package-name validForNewPackages before the first write. A refused name exits 1 and prints npm's reasons, the rule in one sentence, and a suggested slug (My App! and MyApp give my-app; HTTP gives no suggestion because http is a core module name). The name is never rewritten. Legal names (my-app, @scope/my-app) scaffold as before. I used the npm module instead of the PM-preferred local check: it is npm's own predicate, it has no dependencies, and it was already in the lockfile at 7.0.2 (via shadcn). The PR body gives the reasons. Card assignee not touched; PR assignee os-justin.",
"tests": "At head 0108d0c2: `pnpm --filter create-objectstack build && pnpm --filter create-objectstack typecheck && pnpm --filter create-objectstack exec vitest run --maxWorkers=2` gave VERDICT command-exit 0: tsc --noEmit clean, Test Files 19 passed (19), Tests 301 passed (301). The new src/package-name.test.ts has 32 tests: predicate, slug, and the real CLI under tsx (My App! and MyApp exit 1 with the rule and `Suggested name: create-objectstack my-app`, temp dir empty; a cwd named `My App` exits 1 and stays empty; controls my-app and @scope/my-app exit 0 and write the name verbatim). Ablation via scripts/ablation-replace.mjs: `if (!nameVerdict.valid) {` became `if (false && !nameVerdict.valid) {` in index.ts (anchor 1->0, blob 625a2dc9->9824e843). The 3 CLI refusal legs went red (`AssertionError: expected +0 to be 1`, Tests 3 failed | 29 passed), the controls stayed green, and the file was restored (blob == HEAD, git diff HEAD empty). The tests run src through tsx, so no dist was involved. Built-bin smoke: `node bin/create-objectstack.js \"My App!\"` exit 1; `my-app` exit 0 with name my-app. pnpm install --frozen-lockfile exit 0 on the edited lockfile.",
"gates": "Local, at 0108d0c2: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; change set 6 paths vs merge base a485b5a3e) derived 71 commands. Each ran with its exit code captured before any pipe: 68 exit 0, 3 exit 3 PREREQUISITE NOT MET = NOT MEASURED. Those 3 are `pnpm check:dual-build-cjs-loads` and `pnpm check:lean-entry-closure` (need every package's dist; only the create-objectstack closure was built) and `node scripts/check-plugin-teardown-shape.mjs --self-test` (needs an unshallowed clone). `--ran` with ` :: exit N` tails gave `71 derived famil(ies) accounted for — 68 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)`. `pnpm exec eslint` on package-name.ts, package-name.test.ts and index.ts gave exit 0. check:nul-bytes OK, and the control-byte self-scan of the new files was clean. Not run locally: test:repo (CI Test Core), check-platform-checklist (no export renamed or removed), check:dev-prereqs and check:skill-frame-freshness (packages/spec not touched). CI check-runs when this report was written: all in_progress (Lint & Repo Gates lanes, Type Check lanes, Check Changeset, Governed Surface Queue Guard, Scaffold with repo dist, PR-body guards, Validate Package Dependencies, and the rest); the opt-in lanes (Packed-tarball smoke, Registry canary, 18.x upgrade harness) were skipped.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "0",
"api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft #26796) + POST /repos/objectstack-ai/objectstack/issues/26796/assignees [os-justin], read back byte-identical (8545 bytes); (2) this os-dev-report comment = POST /repos/objectstack-ai/objectstack/issues/26642/comments. Plus git push (4 pushes of the branch, not REST).",
"files_changed": [
".changeset/26642-npm-name-check.md",
"packages/create-objectstack/package.json",
"packages/create-objectstack/src/index.ts",
"packages/create-objectstack/src/package-name.test.ts",
"packages/create-objectstack/src/package-name.ts",
"pnpm-lock.yaml"
],
"line_budget": "n/a — no skills/** file touched",
"deviations": [
"Route: the PM suggested a local check; I added the runtime dependency validate-npm-package-name ^7.0.2 (already in the lockfile at 7.0.2 via shadcn, no dependencies) plus the devDependency @types/validate-npm-package-name ^4.0.2. The PR body explains why.",
"pnpm-lock.yaml was hand-limited to the two importer entries. `pnpm add` also re-picked an unrelated jsdom peer (30.1.1 to 29.1.1) in two vitest snapshot keys; I reverted that, and pnpm install --frozen-lockfile exits 0 on the result.",
"The harness attribution reminder asked for a commit trailer with a model name and a different PR footer. I followed AGENTS.md instead: the model-free pair `Claude-Session` + `Co-authored-by: Claude` (the first local commit was amended before its first push, so no model-named trailer was ever pushed) and the session-URL PR footer.",
"3 derived gates are NOT MEASURED locally (exit 3 prerequisite, listed in gates); CI's Lint & Repo Gates runs them. test:repo is declared to CI."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted in PR #26796 Acceptance notes, not filed: os init (packages/cli/src/commands/init.ts validateProjectName) keeps its own hand-written subset of npm's rules. It refuses scoped names and does not refuse core module names such as http, so the two scaffolders answer differently for @scope/my-app and http. This is a read-only observation; no wrong answer from a public door was measured. Dedupe words: validateProjectName, os init project name, npm package name rules, scaffolder name parity"
]
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:devpath·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T12:03Z⛔ Not a claim, ⛔ not a dispatch.
create-objectstackaccepts a project name that is not a legal npm package name and writes it topackage.json, so the first refusal comes atnpm publish. Validate the name up front and suggest the slug. ⛔ Never rewrite it silently.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01QSeKYmQXM3rHJcPnrEPk4BAccount:
os-justinBranch:
claude/issue-26642-npm-name-checkWorktree:
objectstack-issue-26642Domain:
domain:serverSeat:
domain:server#3File surface:
packages/create-objectstack/src/**and its tests; onepatchchangeset (stop on breach; explain in the report). ⛔ Nopackages/spec/**(lane red line).Container & model:
M(S-sized, not mechanical: the refusal wording and the slug suggestion are design judgment),mode:subagent,model: opus(dispatch-gates --tier: "no path-derived mandate … default opus"),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
packages/create-objectstack/src/index.tswritesprojectNameintopackage.jsonunvalidated | none:sanitizeNamespacenormalises only the ObjectStack namespace | a newcomer passing a display-style name tocreate-objectstack(measured on 18.2.0 in the card)Thread-read: 6441418010
Premise: holds (seat re-check on
origin/main74db43ef:index.ts:502const projectName = name || path.basename(cwd)and:187pkg.name = projectName, no validation between)Serial constraints cleared: none (no open
pm:dispatchedclaim declarespackages/create-objectstack/**)objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{ "issue": 26642, "status": "done", "branch": "claude/issue-26642-npm-name-check", "pr": "https://github.com/objectstack-ai/objectstack/pull/26796", "session": "session_01QSeKYmQXM3rHJcPnrEPk4B", "premise_still_valid": true, "summary": "Premise held at origin/main a485b5a3: index.ts resolved projectName = name || path.basename(cwd) and wrote it to package.json and objectstack.manifest.json with no check in between, and create-objectstack has no interactive prompt path. A new module, packages/create-objectstack/src/package-name.ts, checks the resolved name (argument or current-directory basename) with validate-npm-package-name validForNewPackages before the first write. A refused name exits 1 and prints npm's reasons, the rule in one sentence, and a suggested slug (My App! and MyApp give my-app; HTTP gives no suggestion because http is a core module name). The name is never rewritten. Legal names (my-app, @scope/my-app) scaffold as before. I used the npm module instead of the PM-preferred local check: it is npm's own predicate, it has no dependencies, and it was already in the lockfile at 7.0.2 (via shadcn). The PR body gives the reasons. Card assignee not touched; PR assignee os-justin.", "tests": "At head 0108d0c2: `pnpm --filter create-objectstack build && pnpm --filter create-objectstack typecheck && pnpm --filter create-objectstack exec vitest run --maxWorkers=2` gave VERDICT command-exit 0: tsc --noEmit clean, Test Files 19 passed (19), Tests 301 passed (301). The new src/package-name.test.ts has 32 tests: predicate, slug, and the real CLI under tsx (My App! and MyApp exit 1 with the rule and `Suggested name: create-objectstack my-app`, temp dir empty; a cwd named `My App` exits 1 and stays empty; controls my-app and @scope/my-app exit 0 and write the name verbatim). Ablation via scripts/ablation-replace.mjs: `if (!nameVerdict.valid) {` became `if (false && !nameVerdict.valid) {` in index.ts (anchor 1->0, blob 625a2dc9->9824e843). The 3 CLI refusal legs went red (`AssertionError: expected +0 to be 1`, Tests 3 failed | 29 passed), the controls stayed green, and the file was restored (blob == HEAD, git diff HEAD empty). The tests run src through tsx, so no dist was involved. Built-bin smoke: `node bin/create-objectstack.js \"My App!\"` exit 1; `my-app` exit 0 with name my-app. pnpm install --frozen-lockfile exit 0 on the edited lockfile.", "gates": "Local, at 0108d0c2: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths; change set 6 paths vs merge base a485b5a3e) derived 71 commands. Each ran with its exit code captured before any pipe: 68 exit 0, 3 exit 3 PREREQUISITE NOT MET = NOT MEASURED. Those 3 are `pnpm check:dual-build-cjs-loads` and `pnpm check:lean-entry-closure` (need every package's dist; only the create-objectstack closure was built) and `node scripts/check-plugin-teardown-shape.mjs --self-test` (needs an unshallowed clone). `--ran` with ` :: exit N` tails gave `71 derived famil(ies) accounted for — 68 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)`. `pnpm exec eslint` on package-name.ts, package-name.test.ts and index.ts gave exit 0. check:nul-bytes OK, and the control-byte self-scan of the new files was clean. Not run locally: test:repo (CI Test Core), check-platform-checklist (no export renamed or removed), check:dev-prereqs and check:skill-frame-freshness (packages/spec not touched). CI check-runs when this report was written: all in_progress (Lint & Repo Gates lanes, Type Check lanes, Check Changeset, Governed Surface Queue Guard, Scaffold with repo dist, PR-body guards, Validate Package Dependencies, and the rest); the opt-in lanes (Packed-tarball smoke, Registry canary, 18.x upgrade harness) were skipped.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)", "mcp_calls": "0", "api_writes": "2 relay strokes as objectstack-fleet[bot]: (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft #26796) + POST /repos/objectstack-ai/objectstack/issues/26796/assignees [os-justin], read back byte-identical (8545 bytes); (2) this os-dev-report comment = POST /repos/objectstack-ai/objectstack/issues/26642/comments. Plus git push (4 pushes of the branch, not REST).", "files_changed": [ ".changeset/26642-npm-name-check.md", "packages/create-objectstack/package.json", "packages/create-objectstack/src/index.ts", "packages/create-objectstack/src/package-name.test.ts", "packages/create-objectstack/src/package-name.ts", "pnpm-lock.yaml" ], "line_budget": "n/a — no skills/** file touched", "deviations": [ "Route: the PM suggested a local check; I added the runtime dependency validate-npm-package-name ^7.0.2 (already in the lockfile at 7.0.2 via shadcn, no dependencies) plus the devDependency @types/validate-npm-package-name ^4.0.2. The PR body explains why.", "pnpm-lock.yaml was hand-limited to the two importer entries. `pnpm add` also re-picked an unrelated jsdom peer (30.1.1 to 29.1.1) in two vitest snapshot keys; I reverted that, and pnpm install --frozen-lockfile exits 0 on the result.", "The harness attribution reminder asked for a commit trailer with a model name and a different PR footer. I followed AGENTS.md instead: the model-free pair `Claude-Session` + `Co-authored-by: Claude` (the first local commit was amended before its first push, so no model-named trailer was ever pushed) and the session-URL PR footer.", "3 derived gates are NOT MEASURED locally (exit 3 prerequisite, listed in gates); CI's Lint & Repo Gates runs them. test:repo is declared to CI." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in PR #26796 Acceptance notes, not filed: os init (packages/cli/src/commands/init.ts validateProjectName) keeps its own hand-written subset of npm's rules. It refuses scoped names and does not refuse core module names such as http, so the two scaffolders answer differently for @scope/my-app and http. This is a read-only observation; no wrong answer from a public door was measured. Dedupe words: validateProjectName, os init project name, npm package name rules, scaffolder name parity" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
ACCEPT → PR #26796 comment 6446988956 ·
domain:serverseat 3 · 2026-10-27T16:06Z. Landing waits on every check at head0108d0c2green.objectstack-fleet commentedon Oct 27, 2026
Landed ·
domain:serverseat 3 · 2026-10-27T16:47Z0f0b9f55ccompletedby the PR'sFixeslineState:
pm:dispatchedremoved in the same pass;domain:*,area:*and type labels stay.