# What is the NO\_COLOR convention?

NO\_COLOR is a convention in which a command-line tool turns off colored output when the NO\_COLOR environment variable is not empty, keeping logs plain.

Last updated September 29, 2026, 8 min read

## Learning objectives

After reading this article you will be able to:

-   Define the NO\_COLOR convention
-   Explain how a CLI decides to use color
-   Compare NO\_COLOR with FORCE\_COLOR

## Related content

-   [How to test a command-line application](https://specstory.com/learning/cli-and-web/testing-command-line-applications)
-   [Why does CLI output change when piped?](https://specstory.com/learning/cli-and-web/tty-vs-pipe)
-   [What is the difference between stdout and stderr?](https://specstory.com/learning/cli-and-web/stdout-vs-stderr)
-   [What is an agent-native CLI?](https://specstory.com/learning/cli-and-web/agent-native-cli)

## What is the NO\_COLOR convention?

The NO\_COLOR convention is an informal standard under which command-line programs add no color when the `NO_COLOR` environment variable is present and not empty. Any value counts, so `NO_COLOR=1` and `NO_COLOR=false` both turn color off. A user can add `export NO_COLOR=1` to a shell profile to turn off color in every program that follows it.

Terminal colors are ANSI escape codes, bytes that a terminal turns into formatting. Before the convention, each program had its own way to turn them off, and some had none. The [NO\_COLOR website](https://no-color.org/) asks software that adds color by default to check the variable. Software that adds color only when a user asks for it does not need to.

Color is part of the output of a command-line interface (CLI). Testing a [command-line application](https://specstory.com/learning/cli-and-web/testing-command-line-applications) therefore includes runs with `NO_COLOR` set and unset, because the two runs can write different bytes.

## How does NO\_COLOR work?

A program that adds color by default runs a short series of checks before it writes to a stream. A common order, from the highest precedence to the lowest, is:

1.  The program reads an explicit choice, e.g. a `--color=never` flag. The NO\_COLOR website says flags and user config files should override the variable.
2.  The program reads `NO_COLOR`. If it is present and not empty, the program adds no color.
3.  The program reads a variable that forces color, e.g. `FORCE_COLOR`. If it is present and not empty, the program adds color even when output goes to a pipe.
4.  The program reads `TERM`. The value `dumb` means the terminal cannot format text, so the program adds no color.
5.  The program checks whether the stream is a [TTY](https://specstory.com/learning/cli-and-web/tty-vs-pipe), a terminal device it can detect, and adds color only if it is.

Diagram: How a program decides to add color

The first check that answers yes decides the output. Tools disagree about whether NO\_COLOR or FORCE\_COLOR comes first.

The [Command Line Interface Guidelines](https://clig.dev/) list the same signals for turning color off, with `--no-color` as the flag. They also advise checking [stdout and stderr](https://specstory.com/learning/cli-and-web/stdout-vs-stderr) separately, so warnings on stderr can keep their color while stdout goes to a pipe.

When color is on, the program writes sequences that [ECMA-48](https://ecma-international.org/publications-and-standards/standards/ecma-48/) calls Select Graphic Rendition. Each one starts with the escape byte and `[`, holds numbers separated by semicolons, and ends with `m`. `\033[33m` turns text yellow, `\033[1m` makes it bold, and `\033[0m` resets both. A terminal applies each sequence and hides it. A log file keeps the bytes, which `cat -v` shows as `^[[33m`.

## What is an example of NO\_COLOR?

Here is an illustrative example. Acme Co. sells furniture online, and its command-line tool, `acme`, exports orders with `acme export` and prints warnings to stderr in yellow. A developer at Acme asks a [coding agent](https://specstory.com/learning/ai-coding/coding-agent) to "Add a `--color` option to `acme export` that takes auto, always, or never." The work takes five steps:

1.  The agent adds the option to the `use_color()` function below, after the `NO_COLOR` check.
2.  The agent's test, run where `NO_COLOR` is unset, pipes `acme export --color=always`, finds `\033[33m` on stderr, and passes.
3.  A developer with `NO_COLOR=1` in a shell profile runs `acme export --color=always 2>&1 | less -R` and sees no yellow, although the convention lets a flag win.
4.  The developer writes the test below to pin the order.
5.  The agent moves the option check to the top, and all three cases pass.

Python's `os.environ.get()` returns an empty string for an empty variable, which counts as false, so an empty `NO_COLOR` counts as unset:

```python
def use_color(option, stream):
    if os.environ.get("NO_COLOR"):
        return False
    if option != "auto":
        return option == "always"
    if os.environ.get("FORCE_COLOR"):
        return True
    if os.environ.get("TERM") == "dumb":
        return False
    return stream.isatty()
```

The test drops inherited variables whose names end in `_COLOR`, so a local setting cannot change the result. Its second case records Acme's choice that `NO_COLOR` wins over `FORCE_COLOR`:

```python
import os
import subprocess
import pytest

@pytest.mark.parametrize("env_vars, option, colored", [
    ({"NO_COLOR": "1"}, "--color=always", True),
    ({"NO_COLOR": "1", "FORCE_COLOR": "1"}, "--color=auto", False),
    ({"NO_COLOR": "", "FORCE_COLOR": "1"}, "--color=auto", True),
])
def test_color_precedence(env_vars, option, colored):
    env = {k: v for k, v in os.environ.items() if not k.endswith("_COLOR")}
    env.update(env_vars)
    cmd = ["acme", "export", option]
    result = subprocess.run(cmd, capture_output=True, text=True, env=env)
    assert ("\033[" in result.stderr) == colored
```

Before step 5, only the first case fails, because `use_color()` returns before reading the option. This example is simplified. A real CLI would also need cases under a [pseudoterminal](https://specstory.com/learning/cli-and-web/pseudo-terminal), where `auto` turns color on.

## What changes when a coding agent writes the code?

Some coding agents set color variables for the commands they start, e.g. Codex sets `NO_COLOR=1` and `TERM=dumb`, even for a command it runs in a pseudoterminal. A program that follows the convention then writes plain output to the agent, whatever its TTY check returns. The colored path runs mainly when a person types the command.

A test the agent writes can then pass in the agent's plain environment and fail where color returns. A continuous integration (CI) job in [GitHub Actions](https://specstory.com/learning/ci-cd/github-actions-checks) has no terminal, but a team can set `FORCE_COLOR=1` there to keep color in the log. A [golden file](https://specstory.com/learning/test-quality/golden-file-testing) that the agent recorded then fails on escape codes around each warning, one way tests pass locally and [fail in CI](https://specstory.com/learning/ci-cd/tests-pass-locally-fail-in-ci). Code that ignores `NO_COLOR` also sends escape codes into the agent's context when the agent runs it in a pseudoterminal.

A practical adjustment is to set `NO_COLOR`, `FORCE_COLOR`, and `TERM` inside each CLI test instead of inheriting them, with one case for each rule in the precedence order. An [agent-native CLI](https://specstory.com/learning/cli-and-web/agent-native-cli) can also accept `--color=never`, so a caller does not depend on the environment.

## What are the limits of NO\_COLOR?

NO\_COLOR has four limits:

-   **It covers only color.** The NO\_COLOR FAQ answers no when asked whether the variable should turn off bold, underline, or italic text. Those styles use the same sequences, so a [snapshot test](https://specstory.com/learning/testing/snapshot-testing) of [help and version](https://specstory.com/learning/cli-and-web/cli-help-and-version) output can still hold escape codes. A tool that must write plain text can offer a separate flag that removes every style.
-   **It reaches only programs that check it.** A program that never reads the variable still writes codes, and nothing enforces the convention.
-   **It passes to child processes.** A value exported in a shell or a CI job also reaches tests that expect color.
-   **It does not clean what is already written.** Codes already in a log stay there.

A regular expression removes color and style codes from a saved log:

```bash
perl -pe 's/\e\[[0-9;]*m//g' build.log > build-plain.log
```

The pattern matches only sequences that end in `m`. Cursor movement codes end in other letters, e.g. `A` to move up, so they need a wider pattern.

## How is NO\_COLOR different from FORCE\_COLOR?

[FORCE\_COLOR](https://force-color.org/) is the opposite convention. When the `FORCE_COLOR` variable is present and not empty, a program adds color even when the stream is not a terminal. NO\_COLOR removes color that a program adds by default, and FORCE\_COLOR adds color that a TTY check would remove.

When both are set, tools disagree. Python's documentation says `NO_COLOR` takes precedence over `FORCE_COLOR`. Node.js ignores `NO_COLOR` when `FORCE_COLOR` holds a supported value, e.g. `1`, and turns color off for any other value. Its supported values include an empty string, which the FORCE\_COLOR convention treats as unset. Like Node.js, the sample code on the FORCE\_COLOR site lets `FORCE_COLOR` win.

The older [CLICOLOR convention](https://bixense.com/clicolors/) uses `CLICOLOR` to ask for color on a terminal and `CLICOLOR_FORCE` to ask for it everywhere. Its page marks it as deprecated in favor of NO\_COLOR and FORCE\_COLOR, and it lets `NO_COLOR` override both of its variables.

## FAQs

### Does NO\_COLOR need a value?

The NO\_COLOR variable needs a value that is not empty, and any value works. NO\_COLOR=1 and NO\_COLOR=false both turn color off in programs that follow the convention, and an empty NO\_COLOR counts as unset.

### Should NO\_COLOR turn off bold text too?

NO\_COLOR should not turn off bold text, according to the convention's own FAQ, because the variable signals only a wish for no added color. Bold and underline codes can therefore still appear in output that a test expects to be plain.

### How do you strip ANSI escape codes from a log file?

ANSI escape codes can be stripped from a log file with a regular expression that deletes each sequence from the escape byte to its final letter. Color and style codes end in the letter m. Cursor movement codes end in other letters and need a wider pattern.

### What is the CLICOLOR convention?

The CLICOLOR convention is an older pair of variables in which CLICOLOR asks for color on a terminal and CLICOLOR\_FORCE asks for color everywhere. Its own page marks it as deprecated and recommends NO\_COLOR and FORCE\_COLOR instead.

---

Source: [NO_COLOR environment variable | Disable color | SpecStory](https://specstory.com/learning/cli-and-web/no-color)
