err() and raiseError() callsite is lint-checked against it, and the codes are append-only post-v1.0. Wire ralphy into a CI pipeline by matching on the code field; never grep the message (it’s templated and may carry a {detail} that changes between runs).
Shape
Every error writes a JSON payload on stderr:class (see below).
Exit-code class mapping
Mapping lives in
classifyExitCode() in cli/lib/errors/catalog.ts. The contract is append-only: classes can’t be renamed, only added.
The catalog
Codes group by class. Every row carries a message template (with{placeholder} tokens interpolated at raise time) and a remediation hint that names a verb / file / doc — never paraphrasing the message.
User errors (exit 2)
Provider errors (exit 3)
Environment errors (exit 4)
Quality-gate refusals (exit 5)
Runtime + cancellation
Class semantics
User (2) — the user’s input is wrong. Re-running with the same flags will fail the same way. Fix the flag or the resource and retry. Provider (3) — the model provider misbehaved. Transient or model-specific. Retry, switch model, or wait. The hint names the swap. Env (4) — the local machine isn’t ready. Missing key, missing binary, fs permission.ralphy doctor catches all of these without making a model call.
Gate (5) — quality / policy refusal, not a technical failure. Two consecutive gate refusals on the same project is the agent’s stop signal — report concrete options, don’t render over the gate.
Runtime (1) — bug. The CLI surfaces this rather than silently exiting on uncaughtException / unhandledRejection. File an issue with the stderr payload.
Cancelled (130) — SIGINT. Append-only logs and manifests are preserved; re-running resumes from where you left off.
Append-only contract (post-v1.0)
The catalog is locked at v1.0 per01-D-07:
- Renames are forbidden.
- Removals are forbidden.
- Deprecating a code requires
deprecated: trueplusreplacedBy: "E_...". Deprecated codes continue to be emitted for at least one major version. - Adding a new code requires a CHANGELOG entry.
tests/unit/errors-catalog.test.ts enforce the contract:
- Every code matches
/^E_[A-Z][A-Z0-9_]+$/. - Catalog has fewer than 40 entries (v1.0 budget).
- Every entry carries a non-empty
message,hint,relatedDocs. - Hints never restate the message verbatim.
- Hints end with full-sentence punctuation.
- Deprecated entries name a known replacement.
- Placeholder braces are well-formed
{name}.
Placeholder template syntax
Messages and hints carry{name} tokens that interpolate at raise time from the context object:
Wiring into CI
Match on thecode field, not the message:
Related
- Output modes — the stderr shape in context
- Setup and doctor —
E_ENV_KEY_MISSING,E_DEP_MISSINGrecovery - cli/lib/errors/catalog.ts — source of truth
- tests/unit/errors-catalog.test.ts — the contract