Skip to content

fix(cli): require ANSI-capable terminal before colorizing - #3121

Open
mrunalp wants to merge 4 commits into
NVIDIA:mainfrom
mrunalp:cli-color-term-capability/mrunalp
Open

fix(cli): require ANSI-capable terminal before colorizing#3121
mrunalp wants to merge 4 commits into
NVIDIA:mainfrom
mrunalp:cli-color-term-capability/mrunalp

Conversation

@mrunalp

@mrunalp mrunalp commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

--color auto treated any terminal as able to render ANSI, so TERM=dumb openshell ... printed escape sequences into a terminal that shows them literally. This adds a terminal-capability check to the auto branch, leaving the explicit overrides untouched.

Related Issue

Fixes #3120

Changes

  • Add term_supports_ansi(TERM) and consult it in resolve under auto only: a stream is styled when it is a terminal and that terminal renders ANSI.
  • Follow the rule console applies on unix: dumb is not capable, and an unset TERM is not capable because nothing identifies a capable terminal.
  • Treat an empty TERM as unset. This diverges from console, which reads TERM="" as Ok("") and counts it capable because the value is not dumb. An empty value names no terminal type, and every other variable in this module already treats empty as unset, so it is handled the same way. The divergence is documented at the function.
  • Apply the check on unix only. Windows consoles enable virtual terminal processing and do not use TERM as the signal.
  • Pin TERM in the existing pty test — see the note below.

Because the check sits after the explicit branches, precedence is unchanged: --color always and FORCE_COLOR still force styling on a dumb terminal, and --color never and NO_COLOR still suppress it on a capable one.

This is partly a regression from #3026

Worth stating plainly, since it affects how the change should be read. Two of the four styling paths were already correct before #3026:

  • 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, via supports-color.

#3026 overrode both with a single process-wide switch so --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 other two paths, the tracing formatter and the owo-colors wrapper, never had detection; those are a gap rather than a regression. This change fixes all four.

Testing

Verified against the built binary on a pseudo-terminal:

session result
TERM=xterm-256color color
TERM=dumb plain
TERM unset plain
TERM="" plain
TERM=dumb --color always color
TERM=dumb FORCE_COLOR=1 color
TERM=xterm NO_COLOR=1 plain
piped, any TERM plain (unchanged)

6 unit tests covering the capability rule and its interaction with each precedence branch, plus 2 pty integration tests: one asserting TERM=dumb is not styled while a capable terminal in the same harness is (positive control), one asserting --color always still styles a dumb terminal.

Both streams share one pty in the new harness, because the owo-colors table requires both streams to accept escapes — that is the shape of a real interactive session, which is the only place capability matters.

I checked the new tests are not vacuous: reverting the condition to stream_is_terminal alone fails dumb_terminal_is_not_styled_under_auto while the other 10 in the file still pass.

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added/updated (if applicable) — not applicable; CLI-local output formatting, no gateway or sandbox involvement

mise run test passes on the full workspace.

Note for reviewers: an existing test changed

split_streams_stdout_tty now pins TERM=xterm-256color. It previously inherited the ambient value, which did not matter when only is_terminal() was consulted. Now that capability is part of the decision, an unset TERM — common on CI runners — would make that test's positive control fail for environmental reasons. The pin is deliberate, not incidental.

Checklist

  • Follows Conventional Commits
  • Commits are signed off (DCO)
  • Architecture docs updated (if applicable) — not applicable; no subsystem boundary or data-flow change. User-facing docs updated in docs/sandboxes/manage-sandboxes.mdx, and --color behavior in the openshell-cli skill reference.

Follow-up to NVIDIA#3026, raised in review.

`auto` treated any terminal as styleable, so `TERM=dumb openshell ...`
still emitted escapes into a terminal that renders them literally. An
unset TERM had the same problem.

This is partly a regression that NVIDIA#3026 introduced. `console`, which
drives indicatif and dialoguer, already refused to colorize when TERM is
`dumb` or unset, and miette applies the same check through
supports-color. NVIDIA#3026 overrides both with its own switch, so it replaced
two working checks rather than only failing to add one. tracing and the
owo-colors wrapper never had detection, so those two are a gap rather
than a regression.

Add the capability check to the `auto` branch only, matching console's
unix rule: `dumb` is not capable, and an unset TERM is not capable
because nothing identifies a capable terminal. Empty is treated as unset,
which diverges from console — it reads `TERM=""` as capable since the
value is not `dumb` — because an empty value names no terminal type and
every other variable here already treats empty as unset.

Because the check sits after the explicit branches, `--color always` and
FORCE_COLOR still force styling on a dumb terminal, and `--color never`
and NO_COLOR still suppress it on a capable one. TERM is a unix signal;
Windows consoles enable virtual terminal processing and do not set it, so
the check does not apply there.

The existing pty test now pins TERM. It previously inherited the ambient
value, which would make its outcome depend on the environment now that
capability is consulted — CI runners frequently leave TERM unset.

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
@mrunalp
mrunalp requested review from a team, derekwaynecarr and sjenning as code owners September 2, 2026 01:33
@copy-pr-bot

copy-pr-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@elezar

elezar commented Sep 2, 2026

Copy link
Copy Markdown
Member

/ok-to-test 1f0beb1

Signed-off-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>
Signed-off-by: Evan Lezar <elezar@nvidia.com>

@elezar elezar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. I added three follow-up commits after reviewing the implementation:

  • af6eb3c8 — refactor the per-stream ANSI-capability check while keeping the TERM predicate independently testable.
  • 9122a8df — clarify the conservative table-color behavior in the public docs.
  • c8a02a60 — cover a STATUS table with stdout on a TTY and stderr redirected.

These are intended as focused follow-ups, not a change in the PR’s direction. Mrunal, please decide whether you are happy to keep them or would prefer to push back on any of them.

@elezar

elezar commented Sep 2, 2026

Copy link
Copy Markdown
Member

/ok to test

@copy-pr-bot

copy-pr-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

/ok to test

@elezar, there was an error processing your request: E1

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/

@elezar

elezar commented Sep 2, 2026

Copy link
Copy Markdown
Member

/ok-to-test c8a02a6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(cli): --color auto styles terminals that do not render ANSI (TERM=dumb)

2 participants