CLI output has more than one reader.
A person sees the terminal. A shell script reads stdout. CI reads the exit code. An agent may inspect the tail of a log and decide whether to retry, stop, or ask for help. Later, someone pastes the failure into an issue and reconstructs state from whatever survived.
That makes a few boring contracts important:
good:
stdout: machine-readable result
stderr: progress and warnings
exit code: 0 success, nonzero failure
bad:
stdout: mixed status text and JSON
stderr: silent
exit code: 0 when the command failed
Keep stdout parseable when another tool needs it. Keep diagnostics on stderr. Give the same failure the same exit code. If structured output exists, keep its shape stable across releases.
Failure messages are part of the interface too. “Permission denied” may be technically accurate. “Permission denied while reading secrets.json; use –scope repo or move the file under the workspace” gives the next action.
Progress output should also have a stable mode. A human-readable stream can show phases. A JSON mode can expose records such as:
{"status":"done","path":"dist/app.tar.gz"}
The best command works for a person and a pipeline without making either one guess whether a warning is data or whether success actually means success.
Related posts

About Jeremy London
Engineering leader and builder in Denver. I write about AI platforms, agents, security, reliability, homelab infrastructure, and the parts of engineering work that have to survive production.