Documentation
¶
Overview ¶
Package errs defines the mio CLI's typed error model and the stable process exit codes that AI agents and CI branch on.
Every command path that can fail should return (or wrap) a *CLIError so that main.go can translate it into the correct exit code. The exit-code contract is part of the public CLI surface — see the design doc §4.5 — and must not drift.
Index ¶
Constants ¶
const ( ExitOK = 0 // success ExitGeneric = 1 // generic / unexpected error ExitUsage = 2 // bad flags / usage error ExitAuth = 3 // missing or invalid credentials ExitNotFound = 4 // resource not found (404) ExitNeedsConfir = 5 // destructive op needs --yes in a non-TTY ExitRateLimited = 6 // rate limited (429) ExitServer = 7 // upstream server error (5xx) )
Exit codes. These are a stable public contract: agents and CI scripts branch on them, so values must never be reused for a different meaning.
Variables ¶
This section is empty.
Functions ¶
func CodeOf ¶
CodeOf extracts the exit code carried by err. A nil error is ExitOK; any error that is not (and does not wrap) a *CLIError is treated as ExitGeneric.
func ExitCodeForStatus ¶
ExitCodeForStatus maps an HTTP status code to a CLI exit code. Used by the client when an API call returns a non-2xx response so the exit code reflects the failure class even when the body has no structured error.
func HTTPStatusOf ¶ added in v0.13.0
HTTPStatusOf returns the HTTP status the API answered with for err, or 0 when err did not come from an HTTP response.
It walks the WHOLE error chain rather than reading only the outermost *CLIError, returning the first RECORDED status it finds from the outside in (wrappers that carry none are transparent). Commands routinely re-wrap a client error to add context — e.g. scaffold's errs.Wrap(errs.CodeOf(err), fmt.Errorf("page %q: %w", slug, err)) — and that outer wrapper has no status of its own. Stopping at the outermost error would silently drop the API's answer for exactly the paths that need it most, and adding context to an error does not make the server's verdict less true.
Note this deliberately differs from CodeOf, which reads only the outermost *CLIError: a command may intentionally RECLASSIFY the exit code (that is a local judgement about how the process should terminate) while the HTTP status remains a statement of fact about what the API returned.
Types ¶
type CLIError ¶
type CLIError struct {
// Code is the process exit code (one of the Exit* constants).
Code int
// Err is the underlying cause; its Error() string is shown to the user.
Err error
// HTTPStatus is the status the API actually answered with, or 0 when this
// error never came from an HTTP response (a genuinely local failure: bad
// flags, unreadable file, no API key).
//
// Why this field exists (MIO-2656): the JSON:API error envelope main.go
// writes to stderr used to reconstruct its `status` member from Code via a
// reverse lookup. That reverse is lossy BY CONSTRUCTION, because
// ExitCodeForStatus is many-to-one — 401 and 403 both collapse to ExitAuth,
// and 400, 409 and 422 all collapse to ExitUsage. The envelope therefore
// reported 403 as "401", and 409/422 as "400". Agents branch on
// errors[].status: 403-vs-401 decides whether re-authenticating can help,
// and 409-vs-422 decides whether retrying can help, so a rewritten status
// is a broken machine contract. Carrying the real status alongside the
// (deliberately coarse) exit code lets both be right at once.
HTTPStatus int
}
CLIError carries both a human-facing error and the process exit code that should be returned when it propagates to main. It implements error and unwraps to the underlying cause so errors.Is/As keep working.
func NewHTTP ¶ added in v0.13.0
NewHTTP builds a *CLIError for a non-2xx HTTP response: the message is formatted like New, the exit code is derived from status via ExitCodeForStatus, and the status itself is retained on the error so the error envelope can report what the API really answered (MIO-2656).
Deriving the code here rather than taking it from the caller keeps Code and HTTPStatus in lockstep: they can never describe two different failures.
func Wrap ¶
Wrap attaches an exit code to an existing error. If err is nil it returns nil so callers can wrap unconditionally.