How Should a CLI Print Machine-Readable Errors?
From the earlier reference collection. Read the current software blog.
Offer a structured error mode with stable codes and concise details, while keeping diagnostics separate from ordinary output.
Define a small error contract
Scripts need errors they can inspect without scraping prose that may change between releases. A structured mode can emit an object with fields such as code, message, and, when safe, path or record. Keep the code stable and make the message concise enough for a log or terminal.
For example, a missing input could produce {"code":"INPUT_NOT_FOUND","message":"Input file was not found"}. The code is the programmatic key; the message helps a person understand the event. Do not overload one field with a sentence callers must parse.
Keep normal output unambiguous
Choose how structured errors interact with the command's usual output. If the command emits JSON results on standard output, writing an error object there may be appropriate only when the interface defines a consistent envelope for both success and failure. Another design sends diagnostics to standard error and reserves standard output for successful result data.
Document the choice and keep it stable. A pipeline expecting a CSV should not receive a JSON error followed by partial CSV. On failure, avoid emitting an incomplete success-shaped document that encourages downstream code to accept it.
Use codes with clear boundaries
Create codes for meaningful classes of failure, not every sentence variation. INPUT_NOT_FOUND, INVALID_FORMAT, and OUTPUT_WRITE_FAILED might be useful distinctions if callers can respond differently. Avoid renaming a code simply because the human-facing wording improves; version the contract deliberately if a change is unavoidable.
Do not expose secrets, full environment values, or private data in structured details. A path may itself contain sensitive information, so provide controls or redaction where output could be shared. Machine-readable data is easy to retain and forward, which makes data minimization important.
Support both humans and automation
Offer a switch such as --output json or --errors json, with a documented default. Include a nonzero exit status as well: structured content explains the failure, while the status lets a shell decide whether to proceed. Callers should not have to infer failure solely from a particular code or message string.
Test that an error can be parsed, that its code remains stable, and that no stray progress text contaminates the structured stream. The result is a CLI that can explain failure to a person and expose a reliable signal to a script.