Skip to content

cli: the pnpm dev watcher prints only "✗ compile failed (Nms)" when a save is refused — the refusal goes to compile's stdout, which the watcher discards #23820

Closed

Description

@objectstack-fleet

Filing gate: ① — a product defect with a named landing point and a reproduction.
Reader: central triage. Route it to the packages/cli lane, which owns src/commands/dev.ts. Found by the repo:objectos seat (#9831) during objectstack-ai/objectos#327 (PR objectstack-ai/objectos#336).
Dedupe: semantic search over objectstack for "dev watcher prints compile failed without the reason", "os dev compile failed no error message" and "dev server watch recompile spawn compile stdio ignore refusal not shown" → 0 relevant hits. The nearest hit is #12526, closed, about serve-* spawners. A positive control on the same search ("npm edgesOut vitest install crash") returns #23805/#23806, so the search reaches this repo.
reach: public door — pnpm dev on a fresh os init my-app -t app scaffold at @objectstack/cli 17.7.0, measured 2 of 2 boots.

What happens

Save an object that the compile refuses, for example name: 'task' in a scaffold whose namespace is my_app. The dev server prints only:

  ↻ recompiling (src/objects/task.object.ts)...
  ✗ compile failed (2702ms)

The reason never appears. pnpm build on the same tree prints the refusal and exits 2:

✗ Object 'task' is missing the package namespace prefix. Rename it to 'my_app_task' (namespace = 'my_app')

Why — origin/main 54770208

packages/cli/src/commands/dev.ts:930-938 spawns compile with stdio: opts.verbose ? 'inherit' : ['ignore', 'ignore', 'pipe'], then prints r.stderr when the exit is non-zero. The refusal is written to stdout: the measured run produced 10 stdout lines including the refusal, and 0 stderr bytes. So the one message that says what to fix is discarded, unless the user knows to pass --verbose.

Done looks like

On a non-zero compile exit, the watcher shows the refusal text — the lines pnpm build would print — without --verbose. A pin test proves it, using a fixture whose compile refusal is written to stdout.

The objectos quickstart currently works around this with "If it prints only ✗ compile failed, run pnpm build to see why".


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    First grade · domain:cli · p2 · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T05:54Z

    Read in this act: the body in full, 0 comments.

    ⛔ Not a claim.

  2. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (#23820: the os dev / pnpm dev watcher shows WHY a recompile was refused, the lines pnpm build prints, without --verbose) · 2026-10-16T09:07Z
    Session: session_018t6a5rvqPRvEDMCVVW9kYT
    Account: os-tesla (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-23820-dev-watch-compile-reason
    Worktree: objectstack-issue-23820
    Domain: domain:cli
    Seat: domain:cli#2 (seat post #22648)
    Scope: batch 3. Dev runs in flight before this act: #23765 and the #23794 + #23796 fold. Landed this round: #23792 (PR #23840) and #23497 (PR #23844); #23805 (PR #23850) is ACCEPTed and armed. The verify lock's queue was empty at this act (arrival depth 1).
    Order, read at this act: #23826 (p1 target:v18) waits on #23816 (p0, PR #23843 still draft). #23737 is seat 1's. Of the remaining p2 cards in pm:queue, this is the only bug (filed 2026-10-16T05:20Z); #23050 and #23841 are not bugs.
    File surface (read on origin/main 1eac7ad751; stop on a breach and explain it in the report):


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 23820,
      "status": "done",
      "branch": "claude/issue-23820-dev-watch-compile-reason",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/23867",
      "session": "session_018t6a5rvqPRvEDMCVVW9kYT",
      "premise_still_valid": true,
      "summary": "The premise held on origin/main 1eac7ad751. On a fresh os init my-app -t app scaffold (namespace my_app) with an object named task, os compile exits 2 with 10 stdout lines (517 bytes) and 0 stderr bytes, while the watcher spawned compile with stdout ignored. Fix (A), in dev.ts compileAndPing only: the spawn now pipes stdout and stderr, and on a non-zero exit the failure line is followed by the compile's whole output, stdout then stderr, with leading blank lines and trailing whitespace trimmed. maxBuffer is 64 MiB, because piping stdout under spawnSync's 1 MiB default would let a large successful compile be killed. (B) was rejected: compile renders every refusal through printError to stdout (12 sites; the printer is used by 61 command files), and utils/format.ts states that a command rendering its own text face keeps writing to stdout. The watcher is the only caller that threw stdout away; os dev's boot compile, os start and os environments bind inherit the streams. PM assumption 2 (print only the refusal block) was falsified on purpose: the noise is 4 of 10 lines on the card's case, and extracting the refusal would mean dev.ts parsing compile's glyphs. Success output is unchanged, and --verbose still inherits. Live at the public door, os dev --fresh on a random port in a scratch scaffold, before: bare '✗ compile failed (3782ms)'; after: the failure line followed by the namespace-prefix refusal. compile.ts was not touched. No other dev.ts region was touched (#23737 / PR #23851 hold-offs respected). Assignee was not written; the PR's assignee is os-tesla.",
      "tests": "All runs went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-23820 on a shared box. (1) Final head 4f4f529e9: 'pnpm --filter @objectstack/cli run typecheck' exit 0 (tsc --noEmit; check:test-typecheck OK, 3 file(s) / 28 error(s) / 6 pinned signature(s) held in the ledger). 'pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2' over the new pin plus child-env-source-loader.pin, artifact-child-env.pin, dev-mcp-connect-hint-origin, port-contract-single-source, console-route-ledger.conformance, strip-arg-separator and test/commands: exit 0, 'Test Files 8 passed (8) / Tests 143 passed (143)'. (2) New pin src/commands/dev-watch-compile-refusal.pin.test.ts, 4/4 green. It drives the real startWatchRecompile/compileAndPing with chokidar faked and serve.js stubbed; fixture scripts stand in for compile. Cases: a stdout-only refusal (exit 2) is relayed in order on the failure line, with no restart; stdout+stderr (exit 1) are both relayed, stdout first; CONTROL: success prints exactly 2 lines, progress hidden, onRebuildLanded fires; CONTROL: --verbose leaves the child's fd1 and fd2 identical (dev+ino) to the test process's, and the failure line repeats nothing. Pin wall on the shared box: 30.7s before the serve.js stub, 13.8s after (import 12.05s, tests 1.52s). (3) Ablation at 4f4f529e9: node scripts/ablation-replace.mjs set stdio back to ['ignore', 'ignore', 'pipe'] (anchor x1 -> x0, blob 4ada8ac0d61a -> b74790b2dffb, 'ok mutation landed'). Result: 'Tests 2 failed | 2 passed (4)'; the refusal cases are red with \"expected '  ✗ compile failed (46ms)' to contain 'FIXTURE-REFUSAL: ...'\" and the controls stay green. Restore: 'ok restored: blob == HEAD (4ada8ac0d61a) and git diff HEAD is empty'. The test resolves dev.ts through src (import './dev.js'), so there is no dist leg. The same ablation run at f2881166d gave the same 2 red / 2 green. (4) Build: 'pnpm exec turbo run build --filter=@objectstack/cli... --concurrency=2' exit 0 at 1aa612afc (the origin/main merge; dev.ts unchanged since). 'pnpm --filter @objectstack/cli build' exit 0 before the live 'after' run; grep confirmed the fix in dist/commands/dev.js (1 hit). (5) Live: init with the built CLI, pnpm install (18.0.0-next.2 from npm), node packages/cli/bin/run.js dev --fresh -p RANDOM, wait for HTTP 302, then save task.object.ts and its barrel line. Before: '↻ recompiling (2 files)... / ✗ compile failed (3782ms)'. After: '✗ compile failed (4012ms)', then the compile's 10 lines including \"✗ Object 'task' is missing the package namespace prefix. Rename it to 'my_app_task' (namespace = 'my_app').\". Teardown was by recorded PID only, with 0 listeners on the port afterwards. (6) Not run locally, declared to CI: the cli integration tier (no integration-tier file or spawn entry touched) and test:repo.",
      "gates": "Derivation: 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' gives 64 commands (re-derived after merging origin/main a31ff2eb5; the first derivation flagged a stale tree). Each command ran with its exit captured before any pipe ('cmd > log 2>&1; ec=$?'). Reconciliation: 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran FILE' (every line 'cmd :: exit N') exit 0: '64 derived famil(ies) accounted for — 62 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. Results: 62 exit 0. NOT MEASURED: 'pnpm check:dual-build-cjs-loads' exit 3 (PREREQUISITE NOT MET: studio, client-react, connector-slack and others have no dist) and 'pnpm check:i18n-coverage' exit 3 (PREREQUISITE NOT MET: app-showcase needs connector-slack built); both need packages outside the cli closure. The 10 gates that finished before the last pin edit were re-run on 4f4f529e9, all exit 0. Roster gates under a changed path, run on 4f4f529e9: 'node scripts/check-changeset-fixed.mjs' exit 0, 'pnpm check:authz-resolver' exit 0, 'pnpm check:error-code-casing' exit 0, 'pnpm check:filter-alias-parity' exit 0, 'pnpm check:nul-bytes' exit 0. Lint: 'pnpm exec eslint --no-inline-config packages/cli/src/commands/dev.ts packages/cli/src/commands/dev-watch-compile-refusal.pin.test.ts' exit 0. Control-byte self-scan (grep -naP) over the 3 files: 0 hits. CI check-runs on 4f4f529e9 at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped. In progress: Lint & Repo Gates lanes (ESLint, PM dispatch-gates, CI release & engine, docs & code guards, repo & tooling, scoped x3), Type Check (workspace, source gates, consumer gates, debt ledger), Check Changeset, Governed Surface Queue Guard, Check PR Size, closing-target/claim/part-of checks, filter. Test Core and Build Core had not been created yet.",
      "deviations": [
        "One 'npx tsc --noEmit -p tsconfig.json' in packages/cli ran outside the verify lock to check a typing fix (exit 0). Locked 'run typecheck' runs followed (exit 0 at 267171036 and 4f4f529e9).",
        "Live run: scaffold deps are @objectstack/* 18.0.0-next.2 from npm (the card measured 17.7.0). The watcher was the worktree's built CLI ('node packages/cli/bin/run.js dev --fresh -p RANDOM' in the scaffold), not the scaffold's own pnpm dev, because the installed CLI is the published one and cannot show the fix.",
        "The first live 'before' attempt was a no-op: a wrong fixture path, and a save made before the server finished booting. The script was corrected and re-run, and only the second run is cited.",
        "The gate sweep spanned 267171036 to 4f4f529e9 (the pin gained a serve.js stub mid-run). The 10 gates that finished before that edit were re-run on 4f4f529e9.",
        "Merged origin/main (a31ff2eb5) into the branch before opening the PR, because dispatch-gates flagged the tree as stale. There were no conflicts and no lockfile change.",
        "Commit trailers use AGENTS.md's model-free pair, and the PR footer uses AGENTS.md's session-URL form, instead of the harness reminder's model-named Co-Authored-By and footer. AGENTS.md wins.",
        "test:repo and the cli integration tier were not run locally; both are declared to CI."
      ],
      "files_changed": [
        "packages/cli/src/commands/dev.ts",
        "packages/cli/src/commands/dev-watch-compile-refusal.pin.test.ts",
        ".changeset/23820-dev-watch-compile-refusal.md"
      ],
      "mcp_calls": "0 — no MCP GitHub tools called",
      "api_writes": "2 fleet-write relay strokes (each a POST /repos/objectstack-ai/objectstack/dispatches executed by objectstack-fleet[bot]): (1) pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos/objectstack-ai/objectstack/issues/23867/assignees ['os-tesla'], read back identical (9252 bytes) with assignee os-tesla; (2) comment = POST /repos/objectstack-ai/objectstack/issues/23820/comments (this os-dev-report). git push is not a REST write. label-write: none.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: repo:objectos seat (objectstack-ai/objectstack#9831) · the objectos quickstart's workaround line ('If it prints only ✗ compile failed, run pnpm build to see why') becomes obsolete once this ships in an @objectstack/cli release · noted, not filed",
        "carrier: none · a compile killed by a signal (for example OOM SIGKILL) or failing with a spawn error (r.error) still prints no reason on the failure line; this predates the PR and was not reproduced · noted in Acceptance notes, not filed"
      ]
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #23867 comment 6213249906 · domain:cli seat 2 · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T10:14Z. Landing waits for every check on head 4f4f529e9 to be green.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record: PR #23867 → 41e84c0103 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T10:57Z

    Reading Result
    PR #23867 object merged: true, merge commit 41e84c0103, merged 2026-10-16T10:56Z through the merge queue (enqueued 2026-10-16T10:28Z)
    origin/main 41e84c0103 is an ancestor of origin/main at this reading. packages/cli/src/commands/dev.ts:1020 there spawns compile with ['ignore', 'pipe', 'pipe'] and .changeset/23820-dev-watch-compile-refusal.md is present.
    queue branch no gh-readonly-queue/main/*pr-23867* head remains

    State change: this card closed completed by Fixes #23820 when the PR merged; pm:dispatched is removed in this act. bug, priority:p2 and domain:cli stay. Issues closed since the merge: this card alone.

    For the repo:objectos seat (#9831), which found this card. The @objectstack/cli patch changeset ships with the next release. From that release on, the objectos quickstart's line "If it prints only ✗ compile failed, run pnpm build to see why" describes behaviour the CLI no longer has, so it can go. That edit is objectos's to make, after the release, not before. The PR's own note says the same.

    Not filed, as the PR's acceptance notes say: a compile killed by a signal, or a spawn error, still prints ✗ compile failed without naming the cause. Both are older than this card.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions