ish command exits with a semantic code. Branch on the code in CI and agent loops, and
parse the JSON envelope (in --json mode) for the structured cause. The code answers “what
class of failure”, the envelope answers “what exactly, and what to do next”.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. |
| 1 | General error (including billing 403: out of credits or usage limit). |
| 2 | Usage or validation error (bad flags, missing arguments). |
| 3 | Authentication failure (no or invalid token). |
| 4 | Not found (the workspace, study, or other entity does not exist). |
| 5 | Transient error, safe to retry. |
--json mode, errors carry a structured envelope: error, error_code, retryable, and often suggestions, error_kind, and an example invocation that fixes the call.
The map is computed by exitCodeFromError in command-helpers.ts. A successful command exits
0; -h, --help and -V, --version also exit 0.
How a failure resolves to a code
The resolver checks the most specific signal first, so a tagged error always beats a status guess and a status guess always beats a message-regex sniff.Billing walls (exit 1)
A
usage_limit_reached or insufficient_credits error is checked before the status map.
These arrive as HTTP 403 but are not auth failures, so they exit 1, not 3. An agent
branching “exit 3 means re-login” would otherwise loop forever on a quota cap.HTTP status (exit 3, 4, 2, 5)
For other API errors: 401 and 403 exit 3, 404 exits 4, 400 and 422 exit 2. A retryable
status (408, 429, or any 5xx) exits 5.
Tagged error code (exit 2, 3, 4)
A CLI-thrown error carrying
error_code is mapped directly: usage_error exits 2,
auth_failed exits 3, not_found exits 4.Retryability and error kind (exit 5, 2)
wait_timeout and any error pre-declaring retryable: true exit 5. A TunnelInactive or
BotAuthError kind exits 5 (the cause is fixable, then retry); ConfirmationRequired or
BotShapeError exit 2.Network cause (exit 5)
DNS and connection failures (
ENOTFOUND, ECONNREFUSED, ECONNRESET, ETIMEDOUT,
EAI_AGAIN) are transient and exit 5.The error envelope
In--json mode (or when stdout is piped, which auto-selects JSON), a failing command writes a
single-line JSON object to stderr and the exit code to the process. Human mode prints
Error: <message> followed by indented → suggestion lines instead. See
global flags for when JSON is auto-selected.
Always present
The human-readable message. For API errors, server-internal entity names are remapped to the
user-facing vocabulary before printing.
The stable machine code to branch on. Prefer this over the message text, which can change.
See the error codes table.
Whether the same call may succeed if retried unchanged.
true corresponds to exit 5.Often present
These fields appear only when the failure carries them, so a consumer must treat each as optional.The HTTP status, present only on errors that came back from the API.
Recovery hints, for example “pass
--study or run ish study use”. Merged from the server
response, the error instance, and the CLI’s own code-to-hint mapping.Field-level validation detail from a 422 response. Each entry carries
loc, msg, type,
and, where the field is an enum, allowed_values.A structured kind for failures that have one, such as
TunnelInactive,
ConfirmationRequired, BotAuthError, or BotShapeError.A corrected invocation that fixes the call, for example the same command with
--yes
appended when a destructive action needs confirmation in --json mode.A one-line hint on a client-side validation error.
The accepted values when a validation error rejected an enum-shaped input.
The set a filter argument could have matched (for example the frames a
--frame value could
resolve to).How far a wait got before it timed out, on a
wait_timeout error.Participants seeded before a dispatch failed. Resume with these rather than re-seeding, which
would create duplicates. A matching
seeded_but_not_dispatched_aliases rides alongside.A hint to report the failure, present only on genuine faults (not on usage errors).
Billing fields
On ausage_limit_reached error, the envelope also carries tier, limit, current, max,
and upgrade_url. See run vs ask for what draws credits.
Error codes
Theerror_code field is the stable contract. API errors map from the HTTP status; CLI-thrown
errors set the code directly.
error_code | Source | Typical exit |
|---|---|---|
auth_failed | API 401 | 3 |
forbidden | API 403 | 3 |
insufficient_credits | API 402, billing 403 | 1 |
usage_limit_reached | billing 403 | 1 |
not_found | API 404, CLI not_found | 4 |
validation_error | API 422, CLI validation | 2 |
usage_error | CLI usage | 2 |
timeout | API 408 | 5 |
rate_limited | API 429 | 5 |
server_error | API 5xx | 5 |
network_error | DNS / connection failure | 5 |
wait_timeout | ish study run wait timer | 5 |
request_failed | other API error | 1 |
client_error | untagged CLI error | 1 |
unknown_error | non-Error throw | 1 |