Skip to content

S0090-cli-exit-2: the exit-code contract cites sysexits accurately - #50

Merged
NSchatz merged 1 commit into
mainfrom
sdd/S0090-cli-exit-2
Aug 29, 2026
Merged

S0090-cli-exit-2: the exit-code contract cites sysexits accurately#50
NSchatz merged 1 commit into
mainfrom
sdd/S0090-cli-exit-2

Conversation

@NSchatz

@NSchatz NSchatz commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Spec: work/specs/S0090-cli-exit-2/spec.md (umbrella).

The defect

The CLI publishes an exit-code contract and grounds it in the Unix sysexits.h header. Under that
framing it labelled exit 2 as EX_USAGE. sysexits.h assigns EX_USAGE the value 64. A
developer aligning their own tooling to the header therefore learned a false fact about it from our
documentation, on the surface an editor renders on hover and the surface a docs site serves.

The label sat in two places in src/core/exit-codes.ts (the module contract table and the USAGE
member JSDoc, which compiles into dist/*.d.ts), and README.md plus
docs-content/concepts-archetype.md grounded their whole table in that header while listing 1 and
2, which it defines at neither value.

What changed

  • Exit 2 carries no sysexits constant. 1 and 2 are both stated as this CLI's own values, in
    the module contract, in each of their table rows, and on both published pages.
  • The four labels that were already correct (EX_DATAERR 65, EX_NOINPUT 66, EX_UNAVAILABLE 69,
    EX_SOFTWARE 70) are untouched.
  • test/exit-code-docs.test.ts is new. It sweeps the README, every source file and every published
    docs page, binds each sysexits constant to the number that surface attributes it to through three
    association rules (member doc comment, table row, nearest number on the line), and reds when the
    two disagree, naming the surface, the line and the code.

What did not change

  • No published number moved. 0/1/2/65/66/69/70 are where they were.
  • No member of the exported EXIT map was added, removed or renamed.
  • test/exit-code-matrix.test.ts is byte identical to its content on the base commit:
    git diff --exit-code origin/main...HEAD -- test/exit-code-matrix.test.ts is empty.
  • No runtime behaviour. No dependency. No docs-content/sidebars.json or docs IA change.

The new guard is not vacuous

Two vacuity traps have sprung in this suite before, so the guard asserts the premise on each side:
the sweep's population is asserted positive and the specific files named; both association rules are
asserted to bind the four correct labels to 65/66/69/70 against the real map; the exact text that
shipped before this correction is replayed as a negative control and must be reported; and that
control has its own control, three correct shapes that must stay silent over a population asserted
non-empty.

Checked end to end as well: restoring the three surfaces from origin/main with this test in place
turns 7 of its 22 cases red, including

src/core/exit-codes.ts:13: EX_USAGE is attributed to exit 2, but sysexits.h assigns it 64
src/core/exit-codes.ts:45: EX_USAGE is attributed to exit 2, but sysexits.h assigns it 64

which are the two defect sites this change removes.

Gates

pnpm typecheck, pnpm lint, pnpm format:check, pnpm check:no-emdash (11 binary excluded, the
count CLAUDE.md records), pnpm check:no-internal-refs, pnpm check:agent-notes, pnpm test
(38 files, 690 tests) and pnpm test:coverage (core 97.84 / 93.95 / 93.28 / 98.12, commands
98.53 / 94.18 / 100 / 99.68, both above the per-directory 90 gate) all pass.

Changeset (patch) and a [Unreleased] changelog entry are included.

Exit 2 was documented as EX_USAGE, and sysexits.h assigns EX_USAGE the
value 64. The label sat twice in src/core/exit-codes.ts (the module's
contract table and the USAGE member JSDoc, which compiles into
dist/*.d.ts and renders on a consumer's hover), and README.md plus
docs-content/concepts-archetype.md grounded their whole table in that
header while listing 1 and 2, which it defines at neither value.

Exit 2 now carries no sysexits constant and both 1 and 2 are stated as
this CLI's own values. The four correct labels (EX_DATAERR 65,
EX_NOINPUT 66, EX_UNAVAILABLE 69, EX_SOFTWARE 70) are untouched.

No published number moved and no member of the exported EXIT map was
added, removed or renamed. test/exit-code-matrix.test.ts is byte
identical to its content on the base commit.

test/exit-code-docs.test.ts is new: it sweeps every source file, the
README and every published docs page, binds each sysexits constant to
the number that surface attributes it to, and reds when the two
disagree, naming the surface, the line and the code. It carries the
header's own table from the cited man pages, reports a constant that
table does not define rather than passing it through, asserts the
premise on each side (the sweep's population, both association rules
against the real map) and replays the exact text that shipped before
this correction as a negative control, with a control of its own for
the shapes that must stay silent.
@NSchatz
NSchatz merged commit 26c4087 into main Aug 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant