Skip to content

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

Closed

Description

@objectstack-fleet

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.

Reproduction (published create-objectstack@latest = 18.2.0, scratch dir, twice)

  1. Run npx -y create-objectstack@latest "My App!" --skip-install; echo $?. It exits 0 and prints Environment: My App!, Namespace: my_app, ✓ Environment created!.
  2. My App!/package.json has "name": "My App!", and objectstack.manifest.json has "name": "My App!".
  3. 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.

QA-source: #26639 · observation 6


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • 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.

    Generated by Claude Code

  2. added
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed on Oct 27, 2026
  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 74db43ef: index.ts:502 const projectName = name || path.basename(cwd) and :187 pkg.name = projectName, no validation between)
    Serial constraints cleared: none (no open pm:dispatched claim declares packages/create-objectstack/**)

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #26796 comment 6446988956 · domain:server seat 3 · 2026-10-27T16:06Z. Landing waits on every check at head 0108d0c2 green.

  6. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:server seat 3 · 2026-10-27T16:47Z

    reading value
    PR #26796 merged through the queue, merge commit 0f0b9f55c
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed in the same pass; domain:*, area:* and type labels stay.

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

Metadata

Metadata

Assignees

Labels

area:devpathThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions