Errors
The shape of a failure, what is stable in it, and what to branch on.
Every failure arrives as one object, in a structured format, on standard output. The exit status carries the same information for a caller that reads nothing else.
{
"error": {
"kind": "not_found",
"code": "app.not_found",
"message": "No application called checkout in this team.",
"hint": "The name is the immutable one, not the display name.",
"next_steps": [
{ "why": "list the applications in this team", "argv": ["outplane", "app", "list"] }
],
"retryable": false
}
}What Is Stable
Branch on kind, on code, or on the exit status. Never on message. The first three
are frozen once shipped and only ever added to. The message is prose and changes with
any release.
| Field | Always Present | What It Is |
|---|---|---|
kind | yes | The coarse class, one of eleven. Each maps to exactly one exit code. |
code | no | The fine identifier, such as app.name_taken. Frozen once published. |
message | yes | A sentence for a person. Not stable. |
hint | no | What to do about it, in one line. |
next_steps | no | Runnable suggestions, each with a why and an argv. |
retryable | yes | Whether running the same thing again could plausibly work. |
retry_after | no | Seconds, when the server said so. |
confirm_command | no | On confirmation_required only. The exact invocation that would proceed. |
details | no | Structured context, such as the fields that failed validation. |
request_id | no | Ties the failure to a server-side record, for support. |
docs_url | no | Where this is documented. |
Each command's page lists the codes it can produce, and outplane schema publishes the
same list for every command at once.
Next Steps Are Runnable
next_steps carries argv rather than a string, so a suggestion can be executed
directly with no shell parsing and no quoting bug turning it into a different command:
"next_steps": [
{ "why": "install the newest version", "argv": ["outplane", "update"] }
]Confirmation Is Not a Failure
A destructive command never prompts. It exits 4 and hands back the command that would
proceed:
{
"error": {
"kind": "confirmation_required",
"code": "confirmation.required",
"message": "Deleting checkout cannot be undone.",
"confirm_command": ["outplane", "app", "delete", "checkout", "--yes", "--confirm-name", "checkout"]
}
}That is the whole protocol. The decision goes to whoever is allowed to make it, and nothing has to be reconstructed by hand.
In a Shell
The exit status is enough for most scripts, and needs no parser:
if ! outplane app get checkout --json > app.json; then
case $? in
3) echo "credential rejected" >&2 ;;
5) echo "no such application" >&2 ;;
8|124) echo "transient, worth retrying" >&2 ;;
esac
exit 1
fiWhen more than the status is needed, read the object:
outplane app delete checkout --json 2>/dev/null \
| jq -r 'select(.error.kind == "confirmation_required") | .error.confirm_command | @sh'In Text Mode
The same failure is a sentence on standard error, with the hint and the next steps
underneath it. details is never rendered there: it exists for a program.