Exit Codes
Animus uses a fixed set of exit codes to classify errors. These codes are stable across versions and safe to match in scripts and CI pipelines.
Exit Code Table
| Code | Name | Description |
|---|---|---|
| 0 | success | Command completed successfully |
| 1 | internal | Unclassified or unexpected error (panics, serialization failures, unhandled conditions) |
| 2 | invalid_input | Invalid arguments, flags, or values supplied by the caller |
| 3 | not_found | Requested resource does not exist (task, workflow, file, etc.) |
| 4 | conflict | Operation conflicts with current state (duplicate resource, concurrent modification) |
| 5 | unavailable | Service or dependency is unreachable (daemon down, runner not responding, connection refused) |
Error Classification
Errors are classified using a typed error system. The CLI wraps errors in a CliError struct carrying a CliErrorKind variant:
| CliErrorKind | code string | exit_code |
|---|---|---|
InvalidInput | "invalid_input" | 2 |
NotFound | "not_found" | 3 |
Conflict | "conflict" | 4 |
Unavailable | "unavailable" | 5 |
Internal | "internal" | 1 |
The classifier walks the anyhow error chain looking for:
- Typed
CliError-- If any error in the chain is aCliError, its kind determines the exit code. - Typed
ClassifiedError-- Shared protocol errors can also carry an explicit kind through the chain. std::io::Errorkinds --NotFoundmaps to exit code 3. Connection-related IO errors (ConnectionRefused,TimedOut,BrokenPipe, etc.) map to exit code 5.- Message-pattern fallback -- Untyped errors fall back to
protocol::classify_error_message, which recognizes stable patterns such asinvalid,not found,already, and connection/unavailable text. - Fallback -- Any unrecognized error defaults to exit code 1 (
internal).
Typed errors take precedence, but truly untyped errors may still be classified by message pattern as a compatibility fallback.
JSON Error Envelope
When --json is active, errors are emitted to stderr as a JSON envelope:
{
"schema": "animus.cli.v1",
"ok": false,
"error": {
"code": "not_found",
"message": "task not found: TASK-999",
"exit_code": 3
}
}The error object may include an optional details field with structured context:
{
"schema": "animus.cli.v1",
"ok": false,
"error": {
"code": "internal",
"message": "daemon failed to start",
"exit_code": 1,
"details": {
"startup_log_tail": "error: panic in scheduler loop"
}
}
}Usage in Scripts
animus subject get --kind task --id task:TASK-001 --json 2>/dev/null
case $? in
0) echo "success" ;;
2) echo "invalid input" ;;
3) echo "not found" ;;
5) echo "service unavailable" ;;
*) echo "unexpected error" ;;
esacHuman-Readable Errors
Without --json, errors print to stderr as plain text using anyhow's alternate formatter, so chained context lines remain visible. For invalid_input errors, a hint is appended suggesting --help:
error: invalid priority '<empty>'; expected one of: critical|high|medium|low
hint: run with --help to view accepted arguments and valuesSee also: JSON Envelope Contract, Global Flags.