Machine answers: --json, message lines and --json-schema
This page is for an agent author or script writer who drives phax and needs to read its answers and act on its failures without parsing text. Every phax command takes --json. With it, stdout carries the command's answer as one JSON value and stderr carries only message lines, one JSON object each. Without it, a command prints the text a person reads, with the same arguments, options, codes and error sentences.
Answers on stdout
A successful command writes its answer on stdout: one JSON value, indented by two spaces, with one trailing newline. It carries the facts, not the text a person reads, with two exceptions where the text is the fact: a paused outcome's lines (the lines phax prints on stderr at that pause) and an already-in-review outcome's message.
- When the answer is a published document (
artifact-status,plan-overview,plan-lint,run-list),$schemais its first key and names the format and the release that shapes it.@lbdremy/phax-schemasreads these. See Read phax files from code. - Any other answer is an object described by the command's contract (see Contracts). It has no
$schema, every key is required, and an absent value isnull.
phax run and phax resume answer { qualifiedName, crossProject, outcome }. The outcome is tagged by kind: dry-run, preview, already-in-review, review_open, stopped, or paused with its reason, phase, resume command, resume-instructions path and, when known, the time the limit resets. phax reset-phase answers preview or reset the same way. The other commands that run or review a run (review-handoff, publish-pr, review-compliance, review-code --headless) answer an object that starts with qualifiedName and crossProject.
Message lines on stderr
Under --json, stderr carries only message lines. Each is one compact JSON object on one line, tagged by kind, and none has a $schema.
For example, the first note of a resume, a held lock and an unknown option:
errornames a failure by its tag.fieldsholds the data the failure carries, andwrittenlists the files the command left behind before failing. Most failures carry the sentence a person reads asfields.message;LockConflictErrorcarries only the run and the holder's pid. The command's--json-schemaentry lists each failure's fields.refusednames thesourceof the field that failed (args,flags,env,stdinorcommand) and itspath.- A refused call is
refused, nevererror: it ran nothing.
Without --json, the same failure is the sentence phax has always written, on stderr, with the same code.
Exit codes
A command's documented codes stay, in both modes. See Exit codes for the full table.
- A verdict code is written with its answer.
phax plans lintexits 1 when a finding is an error and still prints its document.phax runandphax resumeexit 13 at a stop, with thestoppedoutcome on stdout. - A run that pauses answers with its
pausedoutcome on stdout and exits with the pause's code: 8 for a rate or usage limit, 14 for a network failure, 5 for a failed handoff turn, 4 for gates exhausted or a gate step refused, 9 for a phase with no changes, and onresumea refused close with 1, 4, 5, 8 or 14. These codes are also the codes of declared failures of the same commands (an agent invocation failure is 5), so read stdout to tell a pause from a failure: a pause writes an answer, a failure writes anerrorline and nothing on stdout. - 64 is
refused: an unknown command, an undeclared argument or option, a source or document that does not decode, or a terminal read as stdin. It exits before any command runs. - 70 is
defect: a command died with something it did not declare, or its output did not encode. - The phase guard is the exception: a call made inside a phase worktree is refused with exit 1, as one
refusedline under--json. - 130:
phax runorphax resumewas interrupted (SIGINT or SIGTERM). The run's state is saved, and under--jsonitsnotelines (a blank one, then the sentence) name thephax resumecommand that continues it; nothing is written on stdout.
Contracts
phax --json-schema prints the contracts catalogue, and phax <command> --json-schema prints one command's entry. Nothing runs. Both are JSON Schema 2020-12.
The catalogue holds program, version, exits (the refused and defect codes), commands and messages (the shapes of the five lines above). A command's entry holds its name words, input, stdin, output and errors, and equals the entry in the catalogue:
An output that is a published document references its schema by URL. Every other output is described in place: those answers carry no $schema, so the catalogue is the only contract to validate them against.
Documents read from a file or stdin
Two options hand a command a document: --verdicts <file|-> on phax artifact decide (JSON) and --brief <file|-> on phax artifact new spec|plan (Markdown text). The value is a path, or - for stdin. Stdin is not opened when the option is absent.
- A terminal on stdin is refused at once, naming the option, without waiting for input.
- A file that cannot be read, a document that does not decode, or one that does not fit the artifact (another artifact's path, an unknown question or option) is refused before anything runs. Under
--jsontherefusedline names the file or stdin and the field.
Written files
A command reports each file it wrote on this call as a written-file record: { path, format, digest }.
pathis from the repository root. A file under~/.phaxis an absolute path.formatis the document's$schema, ornullwhen the file names none.digestissha256:followed by 64 lowercase hex digits, taken from the file read back after the write.
The records are in the answer on success, and in the written field of the error line when the command fails part way. Under --json, a document answer also gets one written line per file. run, resume, publish-pr and reset-phase report none: their work is commits and phax's own state, and their answers name the paths that matter (finalReport, pullRequest, archivedPath).
Commands that never answer --json
A command that hands the terminal to a session or prompts a person cannot answer --json. Each is refused with exit 64 before anything is read or started, and its refused line names what lifts the refusal.
A few commands refuse --json only in the mode that prompts, and name the flag that avoids it: phax artifact decide without --verdicts and --by, phax artifact approve and reopen without --by, phax init without --yes, phax prune without --yes or --dry-run, and phax records init. phax resume and phax reset-phase never wait on a person: without --yes they answer their preview and exit 0. phax open --json is refused even when the configured editor is a graphical one that would have returned at once.
These commands exit with the code of the process they handed the terminal to, whatever it is, 64 and 70 included. That is only seen without --json.
Progress on long commands
phax run and phax resume take a long time, and write their lines as they go. Without --json, each stays on the stream it always used. Under --json, each becomes a note line at the moment it happens, so an agent follows the run on stderr while stdout stays empty until the answer.
- The lines
phax runandphax resumeprint before their outcome (Run: <name>,using cached extraction, theSkipping …andwould resume …lines) are notes. TheRun: <name>note is not part of the answer, which carriesqualifiedName. --verboseevent lines,phax·errorlines and warnings (the unsafe-mode box,already exists; using …) are notes.- The what's-next lines after a run reaches review are notes. The answer holds the phase count, the pull request, the completions and the final report's path.
- Under
--json, the order of stdout against stderr is not a contract. Within stderr, the order is the order they happened.
Examples
Read the open questions of a spec:
This prints the open list of the versioned artifact-status document, and nothing else.
Follow only the failure of a resume:
On a held lock this prints the LockConflictError line, with the run and the pid in fields, and the call exits 7.
Print one command's contracts:
This prints the entry for plans lint without reading a plan.