Blog

stdout vs stderr for CLI Errors

Write CLI data to stdout and errors to stderr. In JSON mode, put one error object on stderr and keep stdout clean. Scripts break when a warning shares a pipe with the payload.

2026-10-08 · 6 min read · AethelLayer

Pipes only forward stdout

A script that captures your output will not see stderr unless it asks. That is the point. Progress and failures stay off the pipe. The payload stays on stdout. If you print a warning to stdout, the next tool parses it as data and fails in a way that looks like their bug.

Humans on a TTY still see both streams. You do not have to pick a single audience. You have to pick a stream per audience.

JSON mode is a different contract

When the user passes an explicit JSON flag, or stdout is a pipe and your tool's contract says so, emit one JSON error as the last line of stderr. Do not print JSON errors at a human who did not ask for them. Text mode gets a sentence. JSON mode gets code, message, and suggestion.

The exit code stays the thing a caller can trust if they discard both streams. Document which code matches which error kind.

Agents will branch on the code

Coding agents and CI parse CLIs now. A message you reword next week must not be the only signal. Stable codes on stderr, in a documented shape, are what those callers need. The CLI Agent Spec's pattern — code, message, suggestion — is a good minimum.

Do not put the stack in the JSON message field. Put a verbose trace in a log file or a probe. Agents will otherwise paste secrets from a frame into a pull request.

Your copy of the failure can be richer

stderr is for the caller in front of you. The probe is for your team. Both should carry the same code so you can match a support screenshot to a row. Only the probe needs the intent sentence.

If those two copies disagree, fix the CLI. Support should never be translating a poetic stderr line back into the taxonomy.

Capture the failure before they leave

Capture the failure before they leave. Book early access or talk to sales, then install @aethellayer/sdk when you are onboarded.