P2: Structured, Parseable Output
Definition
CLI tools MUST separate data from diagnostics and offer machine-readable output formats. Mixing status messages with data forces agents into fragile regex extraction that breaks on any format change.
Why Agents Need It
An agent calling a CLI needs three things from each invocation: the data, the error (if any), and the exit code. When data goes to stdout, diagnostics go to stderr, and errors carry machine-readable fields, the agent parses the result reliably without heuristics. Mix these channels or ship human-formatted output only, and the agent falls back to best-effort text parsing that fails unpredictably across versions, locales, and edge cases: silently at first, catastrophically later.
Requirements
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General command error |
| 2 | Usage error (bad arguments) |
| 77 | Authentication / permission error |
| 78 | Configuration error |
- When
--output jsonis active, errors are emitted as JSON (to stderr) with at leasterror,kind, andmessagefields. A plain-text error in a JSON run breaks the consumer's parser on the only shape it was told to expect. - (Applies when: CLI emits structured output.) The output schema is exposed at runtime via a
schemasubcommand or a--schemaflag on each data-emitting subcommand. The schema identifies its format (canonical recommendation: JSON Schema 2020-12, the same dialect OpenAPI 3.1 uses), so an agent reading the schema loads the right validator without parsing prose. A consumer asking "what shape am I about to receive?" gets a machine-readable answer in one call.
Evidence
Rust reference implementation:
OutputFormatenum withText,Json,Jsonlvariants derivingValueEnum.OutputConfigstruct withformat,use_color, andquietfields threaded through every output-producing function.serde_jsoninCargo.toml.- No
println!insrc/outside the output module: every print goes throughOutputConfig. - Exit-code constants or match arms mapping error variants to distinct numeric codes.
eprintln!(or an equivalent diagnostic macro) for every diagnostic line.
Anti-Patterns
println!scattered across handlers instead of routing through the output config.- A single exit code (1) for everything: agents cannot distinguish auth failures from config errors.
- Status lines ("Fetching data…") printed to stdout where they contaminate JSON output.
process::exit()in library code, bypassing structured error propagation.- Human-formatted tables as the only output mode with no JSON alternative.
Measured by audit IDs p2-output-json, p2-output-format, p2-stderr-diagnostics. Run anc audit --principle 2 .
against the CLI under test to see each.