ax-check rule

AXC-C003: Error text not on stderr

On an error, the message went to stdout and stderr was empty, so it mixes with output a script or agent parses.

ax-check is a checker being prepared for release. This page documents the rule ahead of that release; see all 50 rules.

Severitywarn
KindObserved. Depends on the program and environment at run time.
Modeax-check probe-cli
Applies toCommand-line programs, run as a subprocess
Pattern tagsexit-codes, error-recovery
Fix in one lineWrite errors and diagnostics to stderr; keep stdout for the result.

On an error, the message went to stdout and stderr stayed empty. Scripts and agents treat stdout as the result. An error mixed into it can be parsed as data.

What it checks

The rule looks at the unknown-subcommand, unknown-flag and missing-value probes, which run only with --probe errors. It fires for a probe that exits non-zero, wrote nothing to stderr and wrote something to stdout. The unknown-subcommand probe only counts when the help output lists subcommands, and the missing-value probe only runs when help documents a flag that takes a value.

Why it matters

Programs that call your CLI usually capture stdout as the result and show or log stderr. If an error lands on stdout, a pipeline such as invoicer list --json | jq . feeds the error text into the next step. The Command Line Interface Guidelines say: “Send messaging to stderr. Log messages, errors, and so on should all be sent to stderr.” Cloudflare’s documentation for its cf CLI describes the same contract: the result goes to stdout as JSON, and messages and errors go to stderr (vendor documentation).

How to fix

Write errors, warnings and progress messages to stderr. Keep stdout for the result only. Most argument parsers have a setting for this, so check how your framework prints usage errors.

Example

Before

$ invoicer --axcheck-unknown-flag </dev/null 1>out.txt 2>err.txt; echo $?
2
$ cat out.txt
error: unknown flag --axcheck-unknown-flag
$ wc -c err.txt
0 err.txt

After

$ invoicer --axcheck-unknown-flag </dev/null 1>out.txt 2>err.txt; echo $?
2
$ wc -c out.txt
0 out.txt
$ cat err.txt
error: unknown flag --axcheck-unknown-flag
Run: invoicer --help

A Python fix:

print("error: unknown flag --axcheck-unknown-flag", file=sys.stderr)
sys.exit(2)

How ax-check detects it

The probe runs with separate pipes for stdout and stderr, so ax-check can tell which stream carried the text. The rule needs three things at once: a non-zero exit, empty stderr and non-empty stdout. If the exit status is 0, the problem is reported by AXC-C002 instead.

To reproduce by hand: invoicer --axcheck-unknown-flag </dev/null 1>out.txt 2>err.txt; wc -c out.txt err.txt.

Known false negatives: a CLI that writes the error to both streams passes. Known false positives: a tool that deliberately prints its usage text to stdout on error, which some older conventions allow. Silence the rule with --disable AXC-C003 when this is intended.

Safety note: The error probes this rule needs are opt-in: run with --probe errors. Without it the rule is not checked, and the report lists it as not checked. Argument parsers normally reject an unknown subcommand, an unknown flag or a missing value before doing any work, but a program that does not validate its arguments will do its ordinary work instead. probe-cli executes your program under your own OS account. The temporary working directory and HOME it uses are not a sandbox: the program can still read and write anything your account can, and use the network. Probe a CLI you have not reviewed only inside a container or a throwaway virtual machine. Run ax-check probe-cli --dry-run -- invoicer first to see the probes that would run, and see the “Probe safety” section of the ax-check README for what is and is not isolated.

Sources

  • Guideline: Command Line Interface Guidelines, clig.dev. https://clig.dev/ . Says to send messaging, including log messages and errors, to stderr.
  • Vendor documentation: Use cf with coding agents, Cloudflare, updated 2026-09-29. https://developers.cloudflare.com/cf/agents/ . Describes an output contract with the result on stdout as JSON, and messages and errors on stderr.

Records in the AX evidence register that share a pattern tag with this rule. A shared tag means the record is about the same pattern, not that it tests this rule. Read the evidence class before the number.