CLI: respect NO_COLOR and never emit ANSI escape codes to stdout
#5980 opened on Apr 10, 2026
Description
Parent: linera-io/linera-protocol#5977 (Tier 1 item 4)
Problem
The colored crate is used to emit ANSI escape codes in the linera CLI output. Two concrete issues:
- ANSI codes leak to stdout in
net up(linera-service/src/cli/net_up_utils.rs:311-330). The command prints shellexportstatements wrapped in.bold(), which emit ANSI escape codes when rendered. Stdout should always be clean text suitable for parsing or piping — never formatted. NO_COLOR/CLICOLORenvironment variables are not explicitly honored. Thecoloredcrate auto-detects TTY but its behavior when piped is not guaranteed to produce clean output, and there's no documented environment override.
Relevant existing usages:
linera-service/src/cli/net_up_utils.rs:6—use colored::Colorize as _;linera-service/src/cli/net_up_utils.rs:317,325,329—.bold()calls on stdoutprintln!s forexport LINERA_WALLET=...etc.linera-service/src/cli/main.rs:36—use colored::Colorize;linera-service/src/cli/main.rs:1473—info!("{}", "Application created successfully!".green().bold());linera-service/src/cli/main.rs:1525—info!("{}", "Application published successfully!".green().bold());linera-service/src/cli/main.rs:1599— same pattern.
Requirements
- Stdout must never contain ANSI escape codes. Any
println!/print!must emit plain text, regardless of TTY status or color settings. - Stderr (via
tracing) may emit color when stderr is a TTY and colors are not disabled. NO_COLOR=1,CLICOLOR=0, andNO_COLORset to any non-empty value must disable all color output on stderr as well. Follow the NO_COLOR specification.CLICOLOR_FORCE=1should force color output even when stderr is not a TTY.
Proposed implementation
For net up (stdout fix):
In linera-service/src/cli/net_up_utils.rs:311-330, remove the .bold() calls. Stdout should be plain:
println!("export LINERA_WALLET=\"{}\"", client.wallet_path().display());
println!("export LINERA_KEYSTORE=\"{}\"", client.keystore_path().display());
println!("export LINERA_STORAGE=\"{}\"", client.storage_path());
The explanatory message at line 306-310 uses eprintln! already, which is correct. If the human wants a bold "HERE IS YOUR CONFIG" banner, it goes on stderr, not stdout.
For NO_COLOR support (stderr):
Early in main() (around linera-service/src/cli/main.rs:1938), check the relevant env vars and call colored::control::set_override(false) if colors should be disabled. The colored crate already reads NO_COLOR on first use, but being explicit ensures consistent behavior. Reference: https://docs.rs/colored/latest/colored/control/fn.set_override.html
fn configure_colors() {
use std::env;
let no_color = env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty());
let clicolor_zero = env::var("CLICOLOR").ok().as_deref() == Some("0");
if no_color || clicolor_zero {
colored::control::set_override(false);
} else if env::var("CLICOLOR_FORCE").ok().as_deref() == Some("1") {
colored::control::set_override(true);
}
}
Call it once at the top of main(). This also affects the .green().bold() calls in main.rs:1473/1525/1599 since those go through tracing, which writes to stderr.
Backwards compatibility
Removing bold from net up stdout is strictly a visual change for humans. The text content is identical. Scripts doing eval "$(linera net up ...)" were already broken by the ANSI codes in some shell configurations — this makes eval actually work consistently.
Testing
Unit test (add to linera-service/src/cli/net_up_utils.rs under #[cfg(test)] mod tests):
Not straightforward since net up is a daemon. Instead, verify via E2E test in linera-service/tests/linera_net_tests.rs:
- Run
linera net up --with-faucetas a subprocess, capture stdout, assert it containsexport LINERA_WALLET=(plain text), assert it does not contain any\x1b[(ANSI escape) sequences. - Run with
NO_COLOR=1env var, assert no ANSI sequences anywhere (stdout or stderr).
Manual verification:
cargo build -p linera-service --bin linera
# Should print plain text to stdout, no ANSI codes
./target/debug/linera net up --with-faucet --faucet-port 8080 2>/dev/null | cat -v
# Look for any '^[' sequences — there should be none in stdout.
# NO_COLOR should also strip colors from stderr
NO_COLOR=1 ./target/debug/linera transfer ... 2>&1 | cat -v
# No ANSI codes anywhere.
Definition of done
-
net upstdout contains no ANSI escape codes. -
NO_COLOR=1andCLICOLOR=0disable colors on stderr. -
CLICOLOR_FORCE=1forces colors even when stderr is not a TTY. - E2E test verifies clean stdout (no ANSI) from
net up. -
cargo clippy --all-targets --all-features -- -D warningspasses. -
cargo fmtapplied. - Manual verification with
cat -vto confirm no escape sequences.
Good first issue
Medium-small change. Touches two files. No architectural questions. Good candidate for a newcomer with basic Rust familiarity.