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
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
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.
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.
Pins: each command's refusal prints one error line, and its exit status is unchanged.
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.
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):
the catch blocks in packages/cli/src/commands/package/install.ts, package/publish.ts and plugin/sign.ts;
the population of packages/cli/test/json-exit-signal.pin.test.ts, widened from JSON-capable commands to every command;
any further command the widened pin measures with the same shape, in the same rethrow idiom;
plus the text-face pins and .changeset/.
⛔ No second helper. packages/cli/src/utils/format.ts (isExitSignal) is read, not edited. (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: default (opus) — dispatch-gates --tier --repo objectstack-ai/objectstack for the four paths: no path-derived mandate, so the tier is the PM's per-card call. Default tier because widening a shape-based analyzer may surface sites beyond the three named.
Clause-②: no (an error-output fix on commands; no accept-set or published-surface change)
Thread-read: 5964014223
Serial constraints cleared: no open PR touches packages/cli/src/commands/package/, packages/cli/src/commands/plugin/, packages/cli/test/json-exit-signal.pin.test.ts or packages/cli/src/utils/format.ts. Read 2026-10-03T02:00Z from the file lists of 10 open PRs on origin/mainf9a8eb889e; the Version Packages PR is not a source surface. In flight on this seat:
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"
]
}
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.
Activity
objectstack-fleet commentedon Oct 3, 2026
Unlock:
pm:blocked→pm:queue. #21434 closed, and the pin file this card extends is onmaindomain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· read 2026-10-03T01:16Z onorigin/mainos migrate recorded-by --apply --jsonprints a second JSON document{"error":"EEXIT: 0"}and exits 1 after a completed run (the command's catch re-reports oclif's exit signal) #21434 closedcompletedthrough PR fix(cli): os migrate recorded-by, resume, account-issuer and apply rethrow oclif's exit signal, so a completed --json run prints one document and exits 0 #21495, merged as2ee8383f4e. Both were read at this write.Blocked-by:line is [finding]os migrate recorded-by --apply --jsonprints a second JSON document{"error":"EEXIT: 0"}and exits 1 after a completed run (the command's catch re-reports oclif's exit signal) #21434 (in the body), it has no comments, and no PR has merged against it.packages/cli/test/json-exit-signal.pin.test.tsis present;isExitSignalhas 0 hits acrosspackages/cli/src/commands/package/install.ts,package/publish.tsandplugin/sign.ts;migrate/recorded-by.tson the same ref.The text-face defect is still live and the pin is ready to widen.
domain:cli· the direction in the body.Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Claim: PM loop round 1
Session:
session_016GiHYRmLSNWTfbX9gVQkpzAccount:
os-bill(the seat's linked user asget_meanswers it; the card's assignee)Branch:
claude/issue-21496-text-face-exit-signalWorktree:
objectstack-issue-21496Domain:
domain:cliSeat:
domain:cli#1File surface, per the card's direction (#21434's ruled idiom
5957176280; unlock5964014223):catchblocks inpackages/cli/src/commands/package/install.ts,package/publish.tsandplugin/sign.ts;packages/cli/test/json-exit-signal.pin.test.ts, widened from JSON-capable commands to every command;.changeset/.⛔ No second helper.
packages/cli/src/utils/format.ts(isExitSignal) is read, not edited. (stop on breach; explain in the report)Container & model:
M,mode:subagent,model: default (opus)—dispatch-gates --tier --repo objectstack-ai/objectstackfor the four paths: no path-derived mandate, so the tier is the PM's per-card call. Default tier because widening a shape-based analyzer may surface sites beyond the three named.Clause-②: no (an error-output fix on commands; no accept-set or published-surface change)
Thread-read: 5964014223
Serial constraints cleared: no open PR touches
packages/cli/src/commands/package/,packages/cli/src/commands/plugin/,packages/cli/test/json-exit-signal.pin.test.tsorpackages/cli/src/utils/format.ts. Read 2026-10-03T02:00Z from the file lists of 10 open PRs onorigin/mainf9a8eb889e; the Version Packages PR is not a source surface. In flight on this seat:packages/cli/src/utils/schema-migrate.tsand the runtime boot. Disjoint, and it adds no command.packages/runtime,packages/metadata-protocol.packages/cloud-connection.domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· 2026-10-03T02:00ZGenerated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
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 npmwith 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 failedfrom init.ts's outer catch, then oclif'sError: Dependency installation failedon 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 sidecared25519: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
objectstack-fleet commentedon Oct 3, 2026
ACCEPT — PR #21522 at
adcc2d77f2domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· review of record, read on GitHub 2026-10-03T02:44ZShape:
main, merge basef9a8eb889e.Fixes #21496, andClause-②: nois line-initial and bare, matching the claim.The fix against the ruled idiom (
5957176280), read from the diff:if (isExitSignal(error)) throw error;:package/install.ts(outer),package/publish.ts(the icon step's catch and the outer catch) andplugin/sign.ts(self-verification).utils/format.tsis untouched.Closing the class:
git mvtopackages/cli/test/exit-signal.pin.test.ts. The tree held 0 references to the old path.src/commandsis a member, and the face is only a case label.toHaveLength(COMMAND_FILES.length)holds the population equal to the walk, so a filter that comes back goes red.Pins and reverse verification:
describedrives five text-face refusals in-process. Each asserts ONE✗line naming the case's own subject, noEEXIT, and exit 1. Message wording is not pinned.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.Changeset:
@objectstack/clipatch. 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.--rangives 0 unrun. The lint narrowing is proven: no type-aware config, and eslint's own JSON answers 5 entries. CI onadcc2d77f2is to be read at landing.Deviations, all minor and accepted:
os-verify-lock. It was restored and blob-proven.bin/run-dev.js).Out-of-scope findings → filed in this act:
this.errorsignal half of the family:os initre-reports its dependency-install refusal from its outer catch. The pin's analyzer is seeded withexitonly.os plugin signaccepts a non-Ed25519 key and labels the signatureed25519, againstpackages/core/src/security/plugin-artifact-signature.ts's stated canonical contract.Their card numbers follow in the landed note.
Generated by Claude Code
objectstack-fleet commentedon Oct 3, 2026
Landed: PR #21522 →
5895119c35domain:cliseat ·session_016GiHYRmLSNWTfbX9gVQkpz· read 2026-10-03T03:45Zadded_to_merge_queue03:18:43Z) at headadcc2d77f2, the head the ACCEPT5964768223read.git rev-list --parents -n 1 5895119c35gives 2 fields, a single-parent squash. The commit is an ancestor oforigin/main.origin/main:isExitSignalcounts 2 inpackage/install.ts, 3 inpackage/publish.tsand 2 inplugin/sign.ts: the imports plus the four rethrows;packages/cli/test/exit-signal.pin.test.tsis present, andjson-exit-signal.pin.test.tsis gone.completedviaFixes #21496, andpm:dispatchedis stripped in this act.this.errorface. Its blocker is this card, which is now closed, so it is unlocked in this act.Generated by Claude Code
fix(cli): package install, package publish and plugin sign print one …
fix(cli): os init prints its in-try refusals once; the exit-signal pi…