Errors and exit codes¶
Exit codes¶
| Exit code | Meaning |
|---|---|
| 0 | Success (status:"ok"). run returns 0 whenever it could start the child; the child's own code is the exit_code field. |
| 1 | General error: bad arguments, missing input, boundary violation, I/O failure, lock timeout, syntax error. |
| 2 | A precondition did not hold: a selector matched nothing or more than one place, a count or hash guard did not match, or the target changed before tx commit or undo. |
| 3 | A read was cut at a limit (status:"partial"). The output is a success, not an error, and carries no error.code. Writes never return 3. |
src/dispatch.lisp maps a command result to these codes: :ok to 0,
:partial to 3, and an error to the exit code its error.code has in the
catalog below.
The error envelope¶
Every error is one JSON object on standard error:
{"schema_version":1,"status":"error","command":"edit",
"error":{"code":"selection.ambiguous","message":"...","exit_code":2,
"repairs":[{"action":"read","detail":"...","command":"aitools read dup.txt"}],
"candidates":[{"line":1,"text":"x"},{"line":2,"text":"x"}]}}
repairsalways has at least one entry.make-error-envelope(packages/core/protocol/src/domain/envelope.lisp) signals an internal error rather than build an envelope without one.repairs[].commandis an aitools invocation to run next. Some repairs name only the command to use (aitools readforcat), not a complete invocation.candidates,diagnostics, andconflictsappear only when the error has them.- When no command-specific repair applies, the repair is
aitools schema <command>.
See JSON output for the field-by-field shape.
error.code catalog¶
The catalog lives in data/domain/protocol/error-catalog-data.lisp
(aitools.data:*protocol-error-codes*); error-code-exit-code in
packages/core/protocol/src/domain/error-catalog.lisp reads it. The table
below is generated from that data and from the error codes each command
schema declares.
"Declared by" lists only the commands whose own schema names the code.
Three codes can also come from the composition root for any command, whether
or not its schema declares them:
argument.invalid for usage errors and --lock-timeout values that are
not durations, environment.busy when recovery cannot take the workspace
lock, and internal.unexpected for an uncaught condition
(src/dispatch.lisp).
When each code occurs¶
| error.code | Occurs when |
|---|---|
argument.invalid |
Argument parsing fails, a value has the wrong type or range, options that exclude each other are combined, a required guard or input is missing, chmod --mode sets a setuid, setgid, or sticky bit (only the low 9 permission bits are allowed), touch --mtime names a date that does not exist (2024-02-30), a numeric table agg aggregate meets a non-numeric column (the message names the column's type), or the command name is unknown. A malformed --range (3, +3, 3: 5) and a --range, --expect-count, duration, or size spelled with digits outside ASCII 0-9 are wrong values too. |
input.not-found |
A file, symbol, JSON Pointer, archive entry, op, tx, bg process, or snapshot does not exist (a true ENOENT). Files and entries come with candidates. A file that exists but cannot be read is environment.io, not this code. |
input.not-utf8 |
A text edit targets a file that is not valid UTF-8. |
input.unsupported-format |
A file is not in a format the command handles (json, table, archive, check), or an encoding is not supported. |
input.unsupported-language |
The code group, --symbol, or transform --op comment meets a language outside the pattern table. |
input.syntax-error |
check finds a syntax error, a regular expression or diff is malformed, a regex is too complex to evaluate (a read --match, --between, or json select --where pattern that hits the regex step limit reports here, not a dedicated code), a value does not parse (util decode, util calc, time convert), or transcode meets bytes it cannot decode. |
selection.no-match |
A selector matches nothing, an apply hunk does not fit, or a JSON Patch test fails. Similar places come as candidates. |
selection.ambiguous |
A selector that must be unique matches more than once. The matches come as candidates. |
selection.count-mismatch |
--expect-count differs from the actual count. |
refusal.target-changed |
--expect-hash differs, or the target changed before tx commit, tx rebase, or undo. The paths come as conflicts. |
refusal.redacted-input |
A write's input contains the literal text [REDACTED_SECRET]. |
refusal.outside-workspace |
A write would land outside the workspace root, through a symlink that leaves it, or inside the workspace's .git/ (its resolved git directory and common directory). archive extract entry paths and copy --recursive children are routed through the same boundary, so a .git/ entry or nested .git/ is refused. The state directory lives outside the root, so writes there are refused as outside-workspace too. |
refusal.exists |
The destination exists and overwriting was not allowed. |
refusal.not-a-file |
A file-only operation targets a directory, a read targets a FIFO, socket, or device (the message says X is not a regular file and the repair is aitools info X), a text edit targets a binary file, delete targets a non-empty directory, or chmod/touch targets a hard-linked file (link count > 1). |
refusal.too-large |
archive extract, archive list, archive read, or copy --recursive exceeds its size or entry limit, or any command's envelope would pass the JSON writer's 16777216-character output limit. In the second case nothing is written to standard output; the error envelope on standard error is the only output, and its repair is aitools schema <command> (ask for less with --max-lines, --limit, or a narrower selector). |
environment.io |
A read or write fails, including a permission-denied (EACCES) read of a file that exists: read, info, check, diff, and write --content-file report this rather than input.not-found for such a file. A failed rename names the path that blocks it, the destination. |
environment.busy |
The workspace lock is not acquired within --lock-timeout. The repair repeats the same command. |
environment.timeout |
wait times out. |
environment.unavailable |
A required external command or OS facility is missing (git outside a repository, no source for sys ports, an argv that cannot start). |
internal.unexpected |
An unexpected condition. The repair is aitools schema <command> when the command is known. |
Unknown command names¶
A name that is not a registered command returns argument.invalid, exit
code 1. src/dispatch.lisp chooses the repairs by the kind of name, in
this order:
- A bare group name (
json,bg,tx) is a real command node with no handler, so the message is<group> requires a subcommandand the repair isaitools schema <group>. That repair is runnable:aitools schemalists a group's subcommands when given the group name (%group-schemasinsrc/schema.lisp), so it no longer fails withinput.not-found. - A group subcommand typed without its group (
get,status,logs,uuid) returns onerun-insteadrepair per group whose subcommand list holds that name, each the grouped form of the invocation with the group token inserted and the globals and operands kept. This is driven by the live registry (%group-subcommand-repairsinsrc/dispatch.lisp), not by the correspondence table, sostatusreturnsaitools bg status,aitools git status, andaitools tx status. - A shell command name in the correspondence table (
cat,grep) returns that row's repairs, drawn fromaitools.data:*correspondence-table*and shown in Using aitools from an agent. - Any other name gets the repair
aitools schema.
$ aitools cat hello.lisp
{"schema_version":1,"status":"error","command":"cat","error":{"code":"argument.invalid","message":"unknown command cat","exit_code":1,"repairs":[{"action":"run-instead","detail":"Read a file with line numbers and range control.","command":"aitools read"}]}}
$ aitools uuid
{"schema_version":1,"status":"error","command":"uuid","error":{"code":"argument.invalid","message":"unknown command uuid","exit_code":1,"repairs":[{"action":"run-instead","detail":"`uuid` is the `util` group's subcommand; include the group name.","command":"aitools util uuid"}]}}
schema also reports near-miss candidates as an agent types them (json get,
not the dotted dispatch name json.get); %typed-name in src/schema.lisp
builds them from the command schemas.