Skip to content

The exit-signal family's text face: os package install, package publish and plugin sign re-report oclif's exit signal as an extra "EEXIT: 1" error line (the remainder of #21434) #21496

Closed

Description

@objectstack-fleet

This card is derived from the in-flight #21434. It carries that family's TEXT-face half. #21434 keeps the JSON-face half, which PR #21495 delivers.

Filing gate: ① a defect with a named position. reach: was measured at a public door by the #21434 dev at 88ae5769c6 (os-dev report 5963586209, out-of-scope finding 1). 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: #21434

What happens (measured)

os package install ./does-not-exist.json prints ✗ Cannot read artifact: ENOENT …, then ✗ EEXIT: 1, and exits 1. The exit status is right; the second error line is the defect.

Why (read from source)

This is #21434's shape, on commands with no JSON face. A this.exit(…) inside the try throws oclif's exit signal, and the catch reports the signal as an error:

  • packages/cli/src/commands/package/install.ts: 6 exit sites inside the try, whose catch is at ~:248;
  • packages/cli/src/commands/package/publish.ts: 15 sites, under the catches at ~:667 and ~:796;
  • packages/cli/src/commands/plugin/sign.ts: 1 site, catch at ~:101.

Direction (#21434's ruled idiom, 5957176280)

Why blocked: this extends the pin file that PR #21495 adds. It starts once that PR lands, which closes #21434.

Dedupe

MCP search_issues, repo-scoped, open and closed: 「package install publish plugin sign prints extra EEXIT error line catch re-reports oclif exit signal」 gave 2 hits, #21285 and #21310. Both are closed and both concern oclif plugins/help text. None covers this.

Dedupe words: EEXIT: 1 extra error line; package install catch re-reports exit signal; plugin sign EEXIT; text-face exit signal.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    area:devpathThe road — create, dev, verify, publish/install, connect an agent, iterate
    on Oct 3, 2026
  2. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

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

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


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:queueReady for the PM dispatch loop
    on Oct 3, 2026
  4. 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-21496-text-face-exit-signal
    Worktree: objectstack-issue-21496
    Domain: domain:cli
    Seat: domain:cli#1
    File surface, per the card's direction (#21434's ruled idiom 5957176280; unlock 5964014223):

    domain:cli seat · session_016GiHYRmLSNWTfbX9gVQkpz · 2026-10-03T02:00Z


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21496,
    "status": "done",
    "branch": "claude/issue-21496-text-face-exit-signal",
    "pr": "#21522",
    "session": "session_016GiHYRmLSNWTfbX9gVQkpz (subagent of the PM session; the claim comment 5964378267 names this branch)",
    "premise_still_valid": true,
    "summary": "The four affected catches now open with the ruled rethrow, if (isExitSignal(error)) throw error;: package/install.ts (outer catch), package/publish.ts (icon-step catch and outer catch) and plugin/sign.ts (self-verification catch). No second helper; format.ts was not edited. The exit-signal pin is renamed (git mv) to packages/cli/test/exit-signal.pin.test.ts, and its population is widened from JSON-capable commands to every module under src/commands. Membership has no predicate; the face is only a case label. A new assertion holds the population equal to the walk, so a filter that comes back goes red. The floors move to 65 commands and 127 sites. A4: I renamed the file and rewrote the header, so its account of how a command enters is true for the widened population. A3, measured BEFORE the fix: the widened red list is exactly 3 members, package install (6 sites), package publish (15 sites, 17 site-catch pairs) and plugin sign (1 site), matching the card. No other command was flagged. A fourth describe drives five text-face refusals in-process: each is ONE error line, no EEXIT, exit 1. Before the fix they printed 2, 2, 2, 3 and 2 error lines.",
    "tests": "All on HEAD adcc2d7 unless noted. (1) pnpm --filter @objectstack/cli exec vitest run --maxWorkers=2 test/exit-signal.pin.test.ts under os-verify-lock: VERDICT command-exit 0, 101 passed. That is 15 fixtures, 3 population checks, 65 members, 13 JSON-face driven cases and 5 text-face driven cases. (2) Related unit tests: vitest run --project unit over the pin, package-install-storage-dir, package-publish-error-envelope, package-publish-manifest-id, package-publish-namespace, package-publish-visibility, plugin-sign, publish-active-environment-store and plugin-publish-visibility. 9 files, 172 passed. (3) Integration tier for the edited install.ts: vitest run --project integration over package-install-local-boot-steps and package-install-local-handlers, which spawn os package install against a live runtime. 2 files, 24 passed. (4) pnpm --filter @objectstack/cli run typecheck: exit 0. check:test-typecheck says OK, ledger unchanged (3 files, 28 errors, 6 signatures); tsconfig.test.json includes test/**. (5) Widened pin BEFORE the fix: f9a8eb8 source with only the population filter removed (uncommitted at that moment). 3 failed, 93 passed of 96, the members os package install / os package publish / os plugin sign. (6) Reverse verification on HEAD 2b562ed: the three command files were restored to f9a8eb8 by git restore --source, under a trap. isExitSignal counts went from 2/3/2 to 0/0/0, and the blobs were verified equal to the f9a8eb8 blobs. The pin went red as expected: 8 failed, 93 passed of 101 (3 structural, 5 driven). The files were restored to HEAD and proven by hash and an empty git diff HEAD. The pin imports the commands by relative src path, so no dist build was involved. (7) Public door via bin/run-dev.js. Before at f9a8eb8: install of a missing artifact printed 2 error lines (the second was EEXIT: 1), exit 1; publish of a missing artifact printed 2, exit 1; publish --icon-file icon.bmp against a local stub control plane printed 3, exit 1. After at 2b562ed: 1, 1 and 1 lines, exit 1 each. Stderr was empty in all six runs. Plugin sign's site is unreachable with any real key (Ed25519, Ed448, RSA, RSA-PSS, EC and DSA all self-verify), so it is measured in-process through the verifyPayload seam. (8) Lint, delivered as a proven narrowing: eslint --no-inline-config --format json over the 5 added or modified paths, exit 0. The JSON has 5 entries: 4 TS files with 0 errors and 0 warnings, and the .changeset reported as ignored (no matching configuration). Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project, no projectService; its header states it), its plugins are inline AST rules, and the only files it reads at load are two baselines this diff does not touch.",
    "gates": {
    "derived_by": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, run with no paths at HEAD adcc2d7 (merge base f9a8eb8)",
    "derived": 66,
    "run": 66,
    "exit_0": 66,
    "first_run_exit_3_then_rerun_0": [
    "pnpm check:dual-build-cjs-loads",
    "pnpm check:i18n",
    "pnpm check:i18n-coverage",
    "pnpm check:i18n-walk-parity"
    ],
    "rerun_condition": "each answered PREREQUISITE NOT MET because no dist/ existed; 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, owned by CI: 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 (see tests item 8); whole-repo pnpm lint is left to CI",
    "ci": "in_progress at report time; not waited on"
    },
    "line_budget": "n/a",
    "files_changed": [
    ".changeset/21496-text-face-exit-signal.md (new; @objectstack/cli patch)",
    "packages/cli/src/commands/package/install.ts",
    "packages/cli/src/commands/package/publish.ts",
    "packages/cli/src/commands/plugin/sign.ts",
    "packages/cli/test/json-exit-signal.pin.test.ts -> packages/cli/test/exit-signal.pin.test.ts (git mv, then widened)"
    ],
    "deviations": [
    "One 23-second single-file vitest run (a temporary SITE_FLOOR probe through scripts/ablation-replace.mjs, used to read the widened site total of 127) ran WITHOUT os-verify-lock. The slot was set, but the wrapper was omitted. ablation-replace restored the file and proved blob == HEAD and an empty git diff HEAD. Every later build and test went through the lock.",
    "In the scratchpad I overwrote the pre-existing issue-21496/issue.json, a file I had not created, with a fresh REST read of the same card (GET /issues/21496 at about 02:02Z). four-axis.md and comments.json were not touched. All of my own files are under issue-21496/dev/.",
    "PR labels: the dispatch named no specific label. label-write therefore wrote only the PR assignee (os-bill). The repo's labelers had already 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.",
    "The public-door readings ran the CLI from source (packages/cli/bin/run-dev.js) rather than from a built dist/ binary.",
    "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. Following the role file and the dispatch, the commits carry the model-free pair (Co-Authored-By: Claude, plus Claude-Session) and the PR body ends with the session-URL footer block. Reported, not imitated."
    ],
    "mcp_calls": "0 — no MCP GitHub tool was called (reads were unauthenticated single-card REST GETs: issue 21496, its comments, PR 21495 and 21522)",
    "api_writes": "3 REST writes, each through the fleet-write relay. On the seat side each was one POST /repos/objectstack-ai/objectstack/dispatches answered 204. (1) pr_create: POST /repos/objectstack-ai/objectstack/pulls opened draft #21522; 10850 bytes sent and stored identical (relay run 37090525683). (2) label-write: POST /repos//issues/21522/assignees with os-bill (relay run 37090576751); read-back matched. (3) This os-dev-report: POST /repos//issues/21496/comments. Not REST: 4 git pushes to claude/issue-21496-text-face-exit-signal (the empty probe, 1439940, 2b562ed, adcc2d7).",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: public door, os init demo -p npm with an unreachable registry (npm_config_registry=http://127.0.0.1:9/) at f9a8eb8. It printed ✗ Project scaffolded, but dependency installation failed., then a second ✗ Dependency installation failed from init.ts's outer catch, then oclif's Error: Dependency installation failed on stderr, and exited 2 · evidence: packages/cli/src/commands/init.ts:1359 and :1388 call this.error inside the run() try. The outer catch (about :1391) does printError and then this.error again, re-reporting the CLIError signal that isExitSignal recognises. This is the same family as #21434 and #21496 through the this.error signal. The pin's analyzer is seeded with exit only, and widening the seed would reshape it, so it is not fixed here; the pin header's does-NOT-cover section states it. Fold it into the exit-signal family's closure, not a point card · dedupe words: os init Dependency installation failed printed twice; init catch re-reports this.error; this.error inside try exit-signal family",
    "class: b · reach: public door, os plugin sign x.osplugin --key rsa.key.pem (an RSA-2048 PKCS#8 key) at f9a8eb8 exits 0, prints ✓ Plugin signed, and writes the sidecar ed25519:default: followed by a 342-character base64url signature; an Ed25519 key gives 86 characters · contract: packages/core/src/security/plugin-artifact-signature.ts header: 'This is the CANONICAL Ed25519 detached-signature contract' and 'Algorithm: Ed25519 via node:crypto'. signPayload and verifyPayload both pass null as the algorithm and never check the key type, so a non-Ed25519 signature is produced, labelled ed25519, and accepted by verifyPayload against a matching non-Ed25519 public key · Seam: spec:none (the contract is stated in the core module header) → runtime:packages/core/src/security/plugin-artifact-signature.ts signPayload/verifyPayload (called by packages/cli/src/commands/plugin/sign.ts) · dedupe words: plugin sign accepts RSA key; ed25519 label on non-Ed25519 signature; verifyPayload key type unchecked",
    "carrier: 承接者:无 · noted, not filed: os plugin sign's self-verification refusal (sign.ts, the verifyPayload false branch) cannot be reached with any real key. Ed25519, Ed448, RSA, RSA-PSS, EC and DSA keys all self-verify. It is a defensive branch, now pinned through a verifyPayload seam. Recorded in the PR's Acceptance notes"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21522 at adcc2d77f2

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

    Shape:

    • Draft, base main, merge base f9a8eb889e.
    • First line Fixes #21496, and Clause-②: no is line-initial and bare, matching the claim.
    • 5 files, +275 / −59. Not governed.
    • 3 commits, each with the model-free trailer pair and no card trailer.

    The fix against the ruled idiom (5957176280), read from the diff:

    • Four catches now open with if (isExitSignal(error)) throw error;: package/install.ts (outer), package/publish.ts (the icon step's catch and the outer catch) and plugin/sign.ts (self-verification).
    • Each change is an import plus one line. ⛔ No second helper, and utils/format.ts is untouched.

    Closing the class:

    • The pin is renamed by git mv to packages/cli/test/exit-signal.pin.test.ts. The tree held 0 references to the old path.
    • Its member predicate is removed: every module under src/commands is a member, and the face is only a case label.
    • A new toHaveLength(COMMAND_FILES.length) holds the population equal to the walk, so a filter that comes back goes red.
    • The floors move from 46 to 65 commands and from 105 to 127 sites, and a separate JSON-face floor of 46 is kept.
    • A3, measured before the fix: the widened red list was exactly the card's 3 members, at 6, 15 and 1 sites.

    Pins and reverse verification:

    • A fourth describe drives five text-face refusals in-process. Each asserts ONE ✗ line naming the case's own subject, no EEXIT, and exit 1. Message wording is not pinned.
    • The fixture keys and files are made at module top, nothing is spawned, and no clocked case is involved.
    • The three command files were restored to f9a8eb889e, which turned 8 cases red (3 structural, 5 driven, which printed 2, 2, 2, 3 and 2 lines). Restoring HEAD brought it back to 101/101.
    • At the public door before and after: 2, 2 and 3 lines became 1, 1 and 1, with exit 1 throughout.

    Changeset: @objectstack/cli patch. Every sentence matches the diff: the three commands, the three-line icon case, and "every exit status is unchanged".

    Gates: 66 derived, all 66 exit 0. Four needed a dist/ build first and then exited 0. --ran gives 0 unrun. The lint narrowing is proven: no type-aware config, and eslint's own JSON answers 5 entries. CI on adcc2d77f2 is to be read at landing.

    Deviations, all minor and accepted:

    • One 23-second single-file vitest probe ran without os-verify-lock. It was restored and blob-proven.
    • The public-door readings ran from source (bin/run-dev.js).

    Out-of-scope findings → filed in this act:

    • The this.error signal half of the family: os init re-reports its dependency-install refusal from its outer catch. The pin's analyzer is seeded with exit only.
    • os plugin sign accepts a non-Ed25519 key and labels the signature ed25519, against packages/core/src/security/plugin-artifact-signature.ts's stated canonical contract.

    Their card numbers follow in the landed note.


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 3, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21522 → 5895119c35

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


    Generated by Claude Code

  8. added 2 commits that reference this issue on Oct 7, 2026
    5895119
    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:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions