Skip to content

The exit-signal family's this.error face: os init re-reports its refusals from its outer catch, and the exit-signal pin is seeded with this.exit only (the remainder of #21496) #21523

Description

@objectstack-fleet

This card is derived from #21496. It carries the family's this.error signal. #21496 carries the this.exit text face, which PR #21522 delivers.

Filing gate: ① a defect with a named position. reach: was measured at a public door by the #21496 dev at f9a8eb889e (os-dev report, out-of-scope finding 1; PR #21522's Acceptance notes). Filed by the domain:cli seat, session_016GiHYRmLSNWTfbX9gVQkpz. Reader who acts: the domain:cli seat dispatches it as a derived sub-issue, with the parent's domain and priority.

Blocked-by: #21496

What happens (measured)

os init demo -p npm with an unreachable package registry prints ✗ Project scaffolded, but dependency installation failed., then a second ✗ Dependency installation failed, then oclif's Error: Dependency installation failed on stderr, and exits 2. One refusal is reported three times.

Why (read from source at origin/main)

this.error(…) throws oclif's CLIError, which carries oclif.exit, so isExitSignal (packages/cli/src/utils/format.ts) already recognises it. In packages/cli/src/commands/init.ts, two this.error calls sit inside run()'s outer try:

  • the scaffold-validation refusal (about :1359);
  • the dependency-install refusal (about :1388).

That try's catch (about :1391) prints the message, then calls this.error again.

The exit-signal pin (packages/cli/test/exit-signal.pin.test.ts after PR #21522; json-exit-signal.pin.test.ts on main today) seeds its analyzer with this.exit only, so this shape is outside it. Over the whole command population at f9a8eb889e the dev counted three this.error calls inside a try. compile.ts:1046 shares its catch with a this.exit(1) the pin already judges green. The other two are init.ts's.

Direction (#21434's ruled idiom, 5957176280)

  • The affected catch rethrows on isExitSignal as its first statement. ⛔ No second helper.
  • Close the class: seed the pin's analyzer with this.error as well as this.exit, over the same every-command population. A later command that throws either signal inside a try enters by existing.
  • Pins: os init's refusal prints its error once, and its exit status is unchanged.

Why blocked: this extends the pin file that PR #21522 renames and widens. It starts once that PR lands, which closes #21496.

Dedupe

MCP search_issues, repo-scoped, open and closed:

Dedupe words: os init Dependency installation failed printed twice; init catch re-reports this.error; this.error inside try exit-signal family; exit-signal pin seeded with exit only.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue. #21496 is closed, and the pin file this card extends is on main

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-03T03:46Z on origin/main


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_016GiHYRmLSNWTfbX9gVQkpz
    Account: os-bill (the seat's linked user as get_me answers it; the card's assignee)
    Branch: claude/issue-21523-this-error-exit-signal
    Worktree: objectstack-issue-21523
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, per the card's direction (#21434's ruled idiom 5957176280; unlock 5965194819):

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-03T03:47Z


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21523,
    "status": "done",
    "branch": "claude/issue-21523-this-error-exit-signal",
    "pr": "#21541",
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz (subagent of the PM session; the claim comment 5965216019 names this branch)",
    "premise_still_valid": true,
    "summary": "The outer catch of packages/cli/src/commands/init.ts now opens with the ruled rethrow, if (isExitSignal(error)) throw error;, imported from utils/format.js. No second helper, and format.ts is not edited. The exit-signal pin's analyzer is seeded with this.error as well as this.exit (SIGNAL_SEEDS), over the same every-command population. Five fixtures were added for the new seed, the header was updated (what seeds the analyzer, how a command enters), and SITE_FLOOR was raised from 127 to 131. Two driven text-face cases pin os init's refusals: ONE ✗ line, exit 2, for a failed install and for a rejected scaffold self-test. A1 confirmed at bee8d1c: isExitSignal had 0 hits in init.ts, and two this.error calls (:1359, :1388) sat inside run()'s outer try, whose catch (:1391) printed and then re-raised. A2 confirmed at the public door (bin/run.js over a fresh dist, npm registry pointed at 127.0.0.1:9): before, two ✗ lines plus oclif's › Error: block, exit 2; after (549f370), one ✗ line plus oclif's block, exit 2. A3, measured BEFORE the fix: the reseeded red list is 1 member (os init) with 2 sites, init.ts:1359 and :1388, both swallowed by the catch at :1391. The 4 this.error-in-try sites are 3 distinct in source: compile.ts:1046 counts for os compile and os build, and its catch is already guarded. No catch handles the signal on purpose or converts it. A4: population floor 65 and JSON-face floor 46 are unchanged (re-measured); the site floor is 131. Interpretation note: 'prints its error once' is implemented as one ✗ line, as in the family's other text-face pins. oclif's own › Error: block for the thrown CLIError still follows, once, as it did before the fix. See open_questions[0].",
    "tests": "All on HEAD 549f370 unless noted. (1) Pin: OS_VERIFY_LOCK_SLOT=issue-21523-dev os-verify-lock.sh -c 'pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/exit-signal.pin.test.ts': VERDICT command-exit 0, 109 passed. Baseline at bee8d1c before any edit: 101 passed. (2) Before the fix (pin edited, init.ts untouched): 3 failed, 106 passed. The failures were os init (text), with the 2 leaks, and both driven os init cases, each printing 2 ✗ lines: install-fail printed '✗ Project scaffolded, but dependency installation failed.' then '✗ Dependency installation failed'; scaffold-refused printed '✗ Scaffold validation failed: exit-signal pin: the rendered config did not load' then '✗ Scaffold validation failed'. (3) Reverse verification after commit 549f370: node scripts/ablation-replace.mjs --file packages/cli/src/commands/init.ts --anchor 'if (isExitSignal(error)) throw error;' --delete -- (the pin run), under the lock. The mutation landed: anchor 1 -> 0, blob 4930c989f594 -> d811c1c322b6. Result: 3 failed, 106 passed, exactly the predicted three; os init (text) names init.ts:1360/:1389 swallowed by the catch at :1392, and each driven case says 'expected [ …(2) ] to have a length of 1 but got 2'. The restore was proven: blob == HEAD 4930c989f594 and git diff HEAD is empty. The subject is imported from src/ by a relative path, so no dist rebuild was involved. (4) cli unit tier: vitest run --project unit --maxWorkers=2, 251 files, 3672 passed, VERDICT 0. The integration tier is declared to CI. (5) The six nightly-tier e2e files that drive os init (init-created-files-summary, starter-field-consumers, scaffold-emission-policy, generate-object-namespace-prefix, generate-scaffolds-reach-stack, create-refuses-invalid-project-name), run with OS_TEST_TIERS=nightly: 6 files, 53 passed, VERDICT 0. Without that switch the filter preflight refused the paths ('matches no test file in this package'): these files are outside the queue population, so that first attempt measured nothing. (6) pnpm --filter @objectstack/cli typecheck: exit 0, and check:test-typecheck is OK with the ledger unchanged (3 files, 28 errors, 6 signatures). tsc -p tsconfig.test.json --listFiles includes test/exit-signal.pin.test.ts with 0 errors in it. (7) Public door, bin/run.js over a fresh dist, from a scratch directory, with npm_config_registry=http://127.0.0.1:9/ and fetch_retries=0. At bee8d1c, os init demo -p npm printed 2 ✗ lines on stdout and '› Error: Dependency installation failed' on stderr, exit 2. At 549f370 it printed 1 ✗ line and the same stderr block, exit 2. (8) Lint, delivered as a proven narrowing: eslint --no-inline-config --format json over the 2 touched TS files gave 2 entries, 0 errors, 0 warnings, exit 0. --print-config shows 5 single-file AST rules on each, with parserOptions.project and projectService null. The diff touches neither eslint.config.mjs nor its baseline JSONs. (9) My own control-byte scan (grep -P) over the 3 changed files found none; check:nul-bytes is OK.",
    "gates": {
    "derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths, at HEAD 549f370 (merge base bee8d1c)",
    "derived": 66,
    "run": 66,
    "exit_0": 66,
    "first_run_exit_3_then_rerun_0": [
    "pnpm check:dual-build-cjs-loads",
    "pnpm check:i18n-coverage"
    ],
    "rerun_condition": "each answered PREREQUISITE NOT MET because no dist/ existed outside the cli closure; after turbo run build --filter='!@objectstack/docs' (72 tasks, 71 cached), each was re-run and exited 0",
    "ran_reconciliation": "dispatch-gates --ran: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN (exit 0)",
    "extra_roster_gates_in_touched_dirs": "node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity: all exit 0",
    "not_measured": "the tool's outside-the-list blocks, which CI owns: 5 path-scheduled CI jobs, 4 type-check lanes, 6 workflow-valued families, 11 wide-population families and the other 50 artifact-roster families",
    "lint": "proven narrowing (tests item 8); whole-repo pnpm lint is left to CI",
    "ci": "in_progress at report time (14 check runs completed with 0 failures, 18 in progress); not waited on"
    },
    "line_budget": "n/a",
    "files_changed": [
    ".changeset/21523-init-refusal-printed-once.md (new; @objectstack/cli patch; carries Clause-②: no)",
    "packages/cli/src/commands/init.ts (+5: the isExitSignal import and the rethrow as the outer catch's first statement)",
    "packages/cli/test/exit-signal.pin.test.ts (+152/-40: SIGNAL_SEEDS, 5 fixtures, the this.error reach check, init anchor, SITE_FLOOR 131, child_process and scaffold-validate seams, a cwd parameter on driveText, an exit parameter on expectOneRefusal, 2 driven os init cases, header)"
    ],
    "deviations": [
    "Base: origin/main had moved from the dispatch tip 5895119 to bee8d1c when the worktree was cut, and the branch is based on bee8d1c. Since then main has gained 550f4cc, which touches serve.ts (a new try with no signal call). git merge-tree of HEAD with origin/main 4916168 is clean.",
    "PR labels: the dispatch named no specific label, so label-write wrote only the PR assignee (os-bill). The repo's labelers applied documentation, size/m, tests and tooling, and the read-back MATCHES. skip-changeset does not apply, because the package publishes and a changeset is present.",
    "Attribution: the harness reminder asked for 'Co-Authored-By: Claude Opus 5.5' and a different PR footer. I followed AGENTS.md and the dispatch instead: a model-free 'Co-Authored-By: Claude' plus a Claude-Session trailer, and the session-URL footer block. No model identifier landed anywhere.",
    "The first e2e invocation (without OS_TEST_TIERS=nightly) ran nothing: the filter preflight refused the paths, exit 1. It was not a measurement and was re-run under the nightly switch. No earlier claim depends on it."
    ],
    "mcp_calls": "0",
    "api_writes": "3 — each a fleet-write relay stroke (POST /repos/objectstack-ai/objectstack/dispatches), executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft, #21541; body read back byte-identical); (2) label-write assign, POST /repos//issues/21541/assignees (os-bill; zero label writes); (3) this os-dev-report comment, POST /repos//issues/21523/comments. Plus 2 git pushes (the empty-branch probe and 549f370), which are not REST writes.",
    "open_questions": [
    {
    "question": "The ruled pin says os init's refusal 'prints its error once'. The ruled idiom removes the catch's second ✗ line. It cannot remove oclif's own '› Error: …' block, which the entry point renders from the thrown CLIError, once, as it did before. Is one ✗ line the intended reading?",
    "options": [
    "A: one ✗ line, which is what is implemented. It matches how the family's other text-face pins count, and it is exactly what the ruled idiom delivers.",
    "B: one rendering of the sentence at the public door. That would need every os init refusal to stop pairing printError with this.error, either by raising with this.exit(2) after the ✗ line or by dropping the ✗ line. That is a change to the command's refusal shape beyond the ruled idiom, and it belongs on its own card (see out_of_scope_findings[0])."
    ],
    "recommendation": "A. It is the ruled idiom's whole effect, and the residual pairing is a separate mechanism with its own population (8 sites in init.ts and compile.ts). Whether that pairing is a defect is a seat call."
    }
    ],
    "out_of_scope_findings": [
    "class: a (candidate; the seat should judge defect vs refusal style) · reach: public door at 549f370, os init demo -t bogus prints '✗ Unknown template: bogus' on stdout, then oclif's '› Error: Unknown template: bogus' on stderr, exit 2: one refusal's sentence twice across two streams · evidence: 8 printError-then-this.error pairings under packages/cli/src/commands (init.ts: the 5 pre-try refusals plus the catch-all; compile.ts: the bundling refusal at :1045-1046 and the catch-all at :1216-1217, whose comment at :1207 names the oclif re-render for the isReportedError case only). format.ts's isReportedError docblock calls 'the operator would read one refusal twice across two streams' the defect it exists to stop · dedupe words: printError then this.error; os init refusal printed twice stdout stderr; oclif Error block repeats the ✗ line; Unknown template printed twice"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21541 at 549f3704e4

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · review of record, read on GitHub 2026-10-03T04:46Z

    Shape:

    • Draft, base main, merge base bee8d1c62c. A git merge-tree against 49161683fb is clean.
    • First line Fixes #21523, and Clause-②: no is line-initial and bare, matching the claim. The footer uses the session-URL form.
    • 3 files, +168 / −40. Not governed.
    • 1 commit, with the model-free trailer pair.

    The fix against the ruled idiom (5957176280), read from the diff: init.ts's outer catch opens with if (isExitSignal(error)) throw error;, imported from utils/format.js. That is +5 lines. ⛔ No second helper, and format.ts is untouched.

    Closing the class:

    • The analyzer is seeded with SIGNAL_SEEDS = ['exit', 'error'], over the same every-command population.
    • 5 fixtures cover the new seed. The header states what seeds the analyzer and how a command enters.
    • Floors: population 65 and JSON-face 46, both re-measured and unchanged. The site floor rises from 127 to 131.
    • A3, measured before the fix: the reseeded red list is exactly 1 member, os init, at 2 sites (:1359 and :1388). compile.ts:1046's catch was already guarded. No catch handles or converts the signal on purpose.

    Pins and reverse verification:

    • Two driven text-face cases: a failed install and a rejected scaffold self-test. Each asserts ONE ✗ line and exit 2.
    • Ablating the rethrow turned exactly the 3 predicted cases red, and the restore was blob-proven.
    • At the public door: 2 ✗ lines became 1, and exit 2 is unchanged.

    The open question ("prints its error once"): answered A, one ✗ line.

    Changeset: @objectstack/cli patch. Each sentence matches the diff, including "followed by oclif's Error: line as before, and the exit status is still 2".

    Gates:

    • 66 derived, all 66 exit 0. Two needed a dist/ build first and then exited 0. --ran gives 0 unrun.
    • The lint narrowing is proven.
    • The nightly e2e files that drive os init gave 53/53.
    • CI on 549f3704e4 is to be read at landing.

    Deviations, all minor and accepted: the base moved before the worktree was cut (merge-tree clean), and no label write beyond the assignee.

    Out-of-scope → filed in this act as #21542: 8 sites in init.ts and compile.ts pair printError with this.error, so a refusal is read twice across two streams. format.ts's isReportedError names that outcome as the defect it exists to stop.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21541 → 24dc7c1134

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · read 2026-10-03T05:36Z


    Generated by Claude Code

  6. added a commit that references this issue on Oct 7, 2026
    24dc7c1
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:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions