Reference
Exit codes
Stable, category-scoped exit codes you can rely on in scripts and automation.
The contract
Exit codes are part of the CLI's script-facing contract: the same category always maps to the same exit code, so automation can branch on the failure mode without parsing stderr. --json never changes the exit code for a given outcome - the machine contract envelope carries the same code in error.exit_code.
Exit code table
| Exit | Category | Typical codes |
|---|---|---|
| 0 | Success | - |
| 1 | Generic failure | UNKNOWN, SERVER_ERROR, UPDATE_FAILED, IDEMPOTENCY_CONFLICT, SESSION_CONFLICT |
| 2 | Invalid usage / arguments (bad invocation) | INVALID_ARGUMENT |
| 3 | Validation failure (execution result, e.g. doctor checks) | VALIDATION_ERROR, PLAN_*, SESSION_INVALID, SESSION_CORRUPT, SESSION_INVALID_TRANSITION |
| 10 | Authentication | UNAUTHENTICATED, INVALID_CREDENTIALS, SESSION_EXPIRED |
| 11 | Permission / authorization | PERMISSION_DENIED, UNAUTHORIZED, AUTHZ_DENIED, AUTHZ_UNAVAILABLE, AUTHZ_INVALID, APPROVAL_REQUIRED, APPROVAL_STALE, APPROVAL_INVALID |
| 12 | Not found | NOT_FOUND, VERSION_NOT_FOUND, ROLLBACK_TARGET_NOT_FOUND |
| 20 | Build | BUILD_FAILED, BUILD_TIMEOUT, TOOLCHAIN_NOT_FOUND, UNSUPPORTED_PROJECT, GIT_SYNC_FAILED |
| 21 | Deploy | DEPLOY_FAILED, INSTANCE_START_FAILED, HEALTH_CHECK_FAILED, DEPLOY_LOCKED, CANARY_REGRESSION, RESOURCE_OOM |
| 22 | Rollback | ROLLBACK_FAILED |
| 23 | Rollback verification failed/cancelled | ROLLBACK_VERIFY_FAILED |
| 24 | Deploy failed and automatic rollback failed | AUTO_ROLLBACK_FAILED (state degraded - manual intervention required) |
| 30 | Network | CONNECTION_ERROR, NETWORK_ERROR, GRPC_ERROR, PORT_UNAVAILABLE |
| 40 | Configuration | CONFIGURATION_ERROR |
| 50 | Docker | DOCKER_ERROR, DOCKER_DAEMON_UNAVAILABLE |
| 60 | Timeout | TIMEOUT |
| 70 | Encryption | ENCRYPTION_ERROR |
Exit 11 uniformly means the execution authorization boundary refused or could not decide, and nothing mutated; the JSON error.code distinguishes denied / unavailable / invalid / approval states, and only AUTHZ_UNAVAILABLE is retryable. Plan staleness keeps exit 3 and its own PLAN_* codes, so “not authorized” and “the world moved on” are never the same signal.
Agent-session record and transition failures are validation results (exit 3: SESSION_INVALID, SESSION_CORRUPT, SESSION_INVALID_TRANSITION). A SESSION_CONFLICT from an --if-rev optimistic-concurrency check is a generic retryable failure (exit 1). Sessions never gate execution, so these never collide with the PLAN_* / AUTHZ_* codes. See Agent execution sessions.
Examples
# App not found -> exit 12phelix status no-such-appecho $? # 12 # Missing required argument (bad invocation) -> exit 2phelix statusecho $? # 2 # doctor ran but a check failed (validation result) -> exit 3phelix doctorecho $? # 3 # Plan refused by the authorization boundary -> exit 11, nothing mutatedphelix plan apply pln_1a2b3c4d5e6f7081echo $? # 11 (--json error.code: AUTHZ_DENIED / APPROVAL_REQUIRED) # Rollback executed but verification window failed -> exit 23phelix rollback myapp --to v7 --verify 30secho $? # 23 (execution succeeded; verification failed)Debugging failures
Run any command with --debug to render the complete error chain on stderr - normal mode bounds it to the first few wrapped layers; --debug lifts that cap. Rendered strings are always run through secret redaction, so debug output never discloses keys, tokens, or encrypted values.