Out Plane

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.

FieldAlways PresentWhat It Is
kindyesThe coarse class, one of eleven. Each maps to exactly one exit code.
codenoThe fine identifier, such as app.name_taken. Frozen once published.
messageyesA sentence for a person. Not stable.
hintnoWhat to do about it, in one line.
next_stepsnoRunnable suggestions, each with a why and an argv.
retryableyesWhether running the same thing again could plausibly work.
retry_afternoSeconds, when the server said so.
confirm_commandnoOn confirmation_required only. The exact invocation that would proceed.
detailsnoStructured context, such as the fields that failed validation.
request_idnoTies the failure to a server-side record, for support.
docs_urlnoWhere 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
fi

When 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.

On this page