ax-check rule
AXC-C011: NO_COLOR not honoured
With NO_COLOR=1 set, output still contained ANSI colour codes.
ax-check is a checker being prepared for release. This page documents the rule ahead of that release; see all 50 rules.
| Severity | warn |
| Kind | Observed. Depends on the program and environment at run time. |
| Mode | ax-check probe-cli |
| Applies to | Command-line programs, run as a subprocess |
| Pattern tags | non-interactive |
| Fix in one line | When NO_COLOR is set and not empty, never add ANSI colour. |
With the environment variable NO_COLOR=1 set, the output still contained ANSI colour codes. NO_COLOR is the standard way for a user, a harness or an agent to say “no colour, please”.
What it checks
The no-color probe runs <cmd> --help with NO_COLOR=1 in the environment. The rule fires when the output still contains ANSI colour sequences, that is, escape codes of the form ESC[...m. Other escape codes, such as cursor movement, are reported by AXC-C010 instead.
Why it matters
Harnesses and users set NO_COLOR when they want clean text, because they cannot always change the command line. The standard at no-color.org says: “Command-line software which adds ANSI color to its output by default should check for a NO_COLOR environment variable that, when present and not an empty string (regardless of its value), prevents the addition of ANSI color.” The Command Line Interface Guidelines list it among the conditions for disabling colour. A program that ignores it leaves the caller with no way to turn the codes off except by stripping them afterwards.
How to fix
Check NO_COLOR before you add any colour. If it is present and not an empty string, add none, whatever its value and whether or not the output is a TTY. Also support --no-color as a flag.
Example
Before
$ NO_COLOR=1 invoicer --help </dev/null | cat -v
^[[1mUsage:^[[0m invoicer <command> [options]
^[[1mCommands:^[[0m
create Create a draft invoice
After
$ NO_COLOR=1 invoicer --help </dev/null | cat -v
Usage: invoicer <command> [options]
Commands:
create Create a draft invoice
A Node fix:
const noColour = Boolean(process.env.NO_COLOR) || process.argv.includes('--no-color');
const bold = (s) => (noColour || !process.stdout.isTTY ? s : `\x1b[1m${s}\x1b[0m`);
A Python fix:
no_colour = bool(os.environ.get("NO_COLOR")) or "--no-color" in sys.argv
How ax-check detects it
The probe sets NO_COLOR=1 and removes FORCE_COLOR and CLICOLOR_FORCE so that the test is fair. It runs --help only, which is read-only for almost every CLI. It then searches stdout and stderr for ANSI colour codes (ESC[...m).
Because the probes already run without a terminal, a program that follows AXC-C010 will pass this one too. This rule mainly catches programs that always add colour, for example because the colour library was configured to force it, or because a help renderer ignores the environment.
To reproduce by hand: NO_COLOR=1 invoicer --help </dev/null | cat -v | grep -n '\^\[\['.
Silence the rule with --disable AXC-C011 if your program colours only help output that is documented as a fixed format.
Safety note: This rule uses the default no-color probe, which runs --help with NO_COLOR=1. 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
- Specification: NO_COLOR, no-color.org. https://no-color.org/ . Says that when
NO_COLORis present and not an empty string, regardless of its value, ANSI colour should not be added. - Guideline: Command Line Interface Guidelines, clig.dev. https://clig.dev/ . Lists
NO_COLORand--no-coloramong the cases where colour should be disabled.
Related evidence
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.
- EV-0036: A CLI exits 0 when a destructive command is refused (Independent measurement). Cloudflare's cf CLI documents that in a non-interactive session a destructive command without --force prints 'Aborted.' and exits with status 0, and a public issue reproduces this for workflows delete.
