Skip to content

refactor(ghost): consistent CLI exit-code contract#198

Draft
nahiyankhan wants to merge 1 commit into
ghost/08-search-and-groundingfrom
ghost/09-exit-code-contract
Draft

refactor(ghost): consistent CLI exit-code contract#198
nahiyankhan wants to merge 1 commit into
ghost/08-search-and-groundingfrom
ghost/09-exit-code-contract

Conversation

@nahiyankhan

Copy link
Copy Markdown
Collaborator

Stacked on ghost/08-search-and-grounding.

A focused cleanup of the CLI exit-code contract, prompted by the gather-ranking work in #197: an agent that branches on exit codes needs them to mean one thing.

The problem

The per-command catch blocks had drifted — the same "something threw" event exited 1 in gather/checks/migrate but 2 in init/skill/review/fingerprint. Worse, several genuine usage errors are surfaced by throw from deep helpers (path validation, --max-diff-bytes, overwrite guards, bad --agent/--template), so any single catch code mislabels them: flat 1 calls a bad flag a crash; flat 2 calls a crash a usage error.

The fix

  • UsageError (in ghost-core, reachable from the scan layer) carries exitCode: 2. The genuine "you called it wrong" throw sites now throw it.
  • failFromError() (shared) reports the message and exits with the error's exitCode, or 1 otherwise. Every command catch routes through it, so the contract can't drift again.
  • Missing package now throws UsageError with a ghost init hint (exit 2) instead of leaking a raw ENOENT.

Net contract: 0 success · 1 ran-but-unhappy (validate findings, or unexpected error) · 2 called-wrong · 3 command-specific refusal (skill already installed). Documented as a table in the CLI reference, including the deliberate split where reporting commands (validate/scan/signals) treat a missing package as a state to report rather than a usage error.

Verification

  • pnpm test (115 passing; +2 contract tests: a thrown usage error exits 2, a missing package exits 2 with guidance)
  • pnpm check green
  • patch changeset added.

Normalize exit codes so an agent can branch on them. Unexpected errors
exit 1; caller mistakes exit 2. The wrinkle was that several genuine
usage errors are surfaced by throwing from deep helpers (path
validation, byte budgets, overwrite guards, bad --agent/--template),
so a flat catch code mislabels them. Introduce a typed UsageError
(ghost-core) carrying exitCode 2 and a shared failFromError() that
exits with a thrown error's exitCode or 1 otherwise, then route every
command catch through it. A missing package now throws UsageError with
a 'ghost init' hint instead of leaking a raw ENOENT, and the exit-code
contract is documented in the CLI reference.
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