User Story
As someone running openshell from a terminal that does not render ANSI — an
editor shell, a serial console, or any environment that sets TERM=dumb —
I want output to be plain text,
so that I read status and error messages instead of the escape sequences meant
to color them.
Problem Statement
--color auto, the default, treats any terminal as able to render ANSI. It
checks only whether the stream is a terminal, not whether that terminal
interprets escape sequences. TERM=dumb is still a terminal, so output is
styled and the escapes are printed literally.
An unset TERM behaves the same way, and nothing identifies a capable terminal
in that case either.
Part of this is a regression introduced by #3026. Two of the four styling paths
were doing the right thing before that change:
console, which draws indicatif progress bars and dialoguer prompts,
refuses to colorize when TERM is dumb or unset
(console-0.15.11/src/unix_term.rs).
miette applies the same check for error rendering, through
supports-color.
#3026 overrides both with a single process-wide switch so that --color could
govern them, and that switch had no capability check. So it replaced two working
checks rather than only failing to add one. The remaining two paths — the
tracing formatter and the owo-colors wrapper — never had detection, so those
are a gap rather than a regression.
Impact / Why This Matters
On a dumb terminal every styled line is now harder to read than the plain text
it replaced. STATUS in forward list reads ^[[31mdead^[[0m, error messages
and -v log lines carry escapes, and progress spinners and prompts do too.
This affects environments where the user cannot simply switch terminals: Emacs
M-x shell and comint buffers set TERM=dumb by design, as do some CI shells,
serial consoles, and build-tool subshells. In those, the escapes are noise
around exactly the text the user is trying to read.
The workaround is to pass --color never or set NO_COLOR on every invocation,
or export NO_COLOR in the shell profile. That works but is not discoverable —
nothing in the output suggests why it looks wrong, and a user seeing ^[[31m
has no obvious reason to reach for a color flag. It also means opting out of
color entirely rather than getting the correct automatic answer.
For the two regressed paths, users who had correct behavior before #3026 now
have to add a workaround they did not previously need.
Acceptance Criteria
Reproduction Steps
This reproduces only on a real terminal. Piping will not show it, because color
is already suppressed for non-terminals.
-
In a terminal, run:
TERM=dumb openshell forward list
(any command with styled output works — openshell gateway list needs no
gateway configured)
-
Observe raw escape sequences in the output rather than color, for example
^[[31mdead^[[0m in the STATUS column.
-
The same with TERM unset:
env -u TERM openshell forward list
To capture it non-interactively, allocate a pseudo-terminal so the CLI still
sees a terminal, then inspect the bytes:
TERM=dumb script -qec "openshell forward list" /dev/null | cat -A
Environment
Logs
# TERM=dumb on a pseudo-terminal, bytes as received
$ TERM=dumb script -qec "openshell forward list" /dev/null | cat -A
SANDBOX BIND PORT PID STATUS$
demo 0.0.0.0 8443 4000000 ^[[31mdead^[[0m$
# The same terminal, opting out by hand
$ TERM=dumb script -qec "openshell forward list --color never" /dev/null | cat -A
SANDBOX BIND PORT PID STATUS$
demo 0.0.0.0 8443 4000000 dead$
User Story
As someone running
openshellfrom a terminal that does not render ANSI — aneditor shell, a serial console, or any environment that sets
TERM=dumb—I want output to be plain text,
so that I read status and error messages instead of the escape sequences meant
to color them.
Problem Statement
--color auto, the default, treats any terminal as able to render ANSI. Itchecks only whether the stream is a terminal, not whether that terminal
interprets escape sequences.
TERM=dumbis still a terminal, so output isstyled and the escapes are printed literally.
An unset
TERMbehaves the same way, and nothing identifies a capable terminalin that case either.
Part of this is a regression introduced by #3026. Two of the four styling paths
were doing the right thing before that change:
console, which drawsindicatifprogress bars anddialoguerprompts,refuses to colorize when
TERMisdumbor unset(
console-0.15.11/src/unix_term.rs).mietteapplies the same check for error rendering, throughsupports-color.#3026 overrides both with a single process-wide switch so that
--colorcouldgovern them, and that switch had no capability check. So it replaced two working
checks rather than only failing to add one. The remaining two paths — the
tracingformatter and theowo-colorswrapper — never had detection, so thoseare a gap rather than a regression.
Impact / Why This Matters
On a
dumbterminal every styled line is now harder to read than the plain textit replaced.
STATUSinforward listreads^[[31mdead^[[0m, error messagesand
-vlog lines carry escapes, and progress spinners and prompts do too.This affects environments where the user cannot simply switch terminals: Emacs
M-x shelland comint buffers setTERM=dumbby design, as do some CI shells,serial consoles, and build-tool subshells. In those, the escapes are noise
around exactly the text the user is trying to read.
The workaround is to pass
--color neveror setNO_COLORon every invocation,or export
NO_COLORin the shell profile. That works but is not discoverable —nothing in the output suggests why it looks wrong, and a user seeing
^[[31mhas no obvious reason to reach for a color flag. It also means opting out of
color entirely rather than getting the correct automatic answer.
For the two regressed paths, users who had correct behavior before #3026 now
have to add a workaround they did not previously need.
Acceptance Criteria
TERM=dumb, default output contains no ANSI escapesequences.
TERMis unset.interactive use is unchanged.
--color alwaysandFORCE_COLORstill force styling on adumbterminal, for callers who know better than the detection.
--color neverandNO_COLORstill suppress styling on a capableterminal.
-vlog lines, progressspinners, prompts, and error rendering — not only the ones that regressed.
--colorand the color environment variables.Reproduction Steps
This reproduces only on a real terminal. Piping will not show it, because color
is already suppressed for non-terminals.
In a terminal, run:
(any command with styled output works —
openshell gateway listneeds nogateway configured)
Observe raw escape sequences in the output rather than color, for example
^[[31mdead^[[0min theSTATUScolumn.The same with
TERMunset:To capture it non-interactively, allocate a pseudo-terminal so the CLI still
sees a terminal, then inspect the bytes:
Environment
mainsince b4afcd8 (fix(cli): suppress ANSI color when stdout is not a terminal #3026); not in a tagged releaseyet — latest tags are v0.0.116 and v0.1.0-pre.1, both of which predate it.
The
consoleandmietteregression is specific to fix(cli): suppress ANSI color when stdout is not a terminal #3026; thetracingandowo-colorsgap predates it.TERMis not thecapability signal on Windows, where consoles enable virtual terminal
processing, so the check should not apply there.
Logs