Skip to content

fix(terminal): let Claude use truecolor so its themed backgrounds render - #409

Draft
irisitymichaelgrundberg wants to merge 2 commits into
Ark0N:masterfrom
irisitymichaelgrundberg:fix/claude-truecolor-in-panes
Draft

fix(terminal): let Claude use truecolor so its themed backgrounds render#409
irisitymichaelgrundberg wants to merge 2 commits into
Ark0N:masterfrom
irisitymichaelgrundberg:fix/claude-truecolor-in-panes

Conversation

@irisitymichaelgrundberg

@irisitymichaelgrundberg irisitymichaelgrundberg commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Claude draws the user's own messages as a block of background color. Inside a Codeman pane that block is invisible, and no Claude theme can change that. This exports COLORTERM=truecolor for Claude, which is what the other seven CLIs already do.

The problem

Two things combine. tmux hands each pane TERM=screen, which supports-color reads as 16 colors, and Claude's registry entry deleted COLORTERM on top of that. Claude then quantizes every RGB color its theme asks for down to the basic palette.

Measured with the chalk version Claude bundles, at 16 colors:

theme color 16-color output result
rgb(55, 55, 55) (built-in dark) ESC[40m the terminal's own black
rgb(28, 48, 84) (a custom theme) ESC[40m the terminal's own black
rgb(240, 240, 240) (built-in light) ESC[47m white

Every dark background lands on ESC[40m, so the block is emitted and paints the terminal's background over itself. Claude's *-ansi themes work, because they name colors like ansi:blackBright and skip the conversion entirely.

Detected levels, same library:

TERM COLORTERM level
screen unset 1 — 16 colors
screen truecolor 3 — 24-bit

Why this reverses #3

#3 added unset COLORTERM in February, reporting that truecolor caused "gray/white backgrounds appearing where they shouldn't" in xterm.js, and citing xtermjs/xterm.js#484. Three things have changed or were already true:

  • xterm.js closed #484 on 2019-04-07, seven years before fix: unset COLORTERM to fix xterm.js rendering issues #3 cited it.
  • Codeman now depends on @xterm/xterm ^6.0.0, which renders 24-bit color.
  • TmuxManager sets terminal-overrides ",*:Tc" on its own tmux server (src/tmux-manager.ts), so 24-bit color already reaches the browser for the CLIs that ask for it.

I want to flag one reading honestly, because I cannot prove it. Suppressing truecolor makes Claude's dark block collapse into the terminal background and disappear. If that is what the reporter saw go away, then the symptom was Claude's user-message block working as designed, not an xterm.js defect.

Changes

  • src/config/cli-registry/stock.ts — Claude's entry exports COLORTERM=truecolor. CLAUDECODE stays unset.
  • src/session-cli-builder.tsbuildClaudeEnv(), the direct-PTY fallback taken when mux creation fails, now reads the same registry entry as the tmux pane and its attach client. It previously deleted COLORTERM from a hand-maintained list of its own, which would have left the fallback contradicting the entry.
  • docs/architecture-invariants.md — a Terminal colour env paragraph under Session launch modes, where a reader looking up Claude will find it. The sentence it replaces sat under a heading listing only the non-Claude CLIs.
  • test/tmux-manager.test.ts — pins the new behaviour beside the existing OpenCode assertion.
  • A changeset.

Claude does not unset NO_COLOR, unlike the other seven truecolor CLIs. The bug does not need it, and unsetting it would override a preference the user set deliberately. The inconsistency is intentional and the entry's comment says so.

An SSH-remote pane is not covered. buildRemoteLaunchCommand() carries no env exports at all, so a remote Claude session keeps the old rendering. That predates this change, and the invariants doc now states it rather than implying every pane is covered. Docker panes take COLORTERM=truecolor from the hardcoded envCreate/execEnv, which apply to every mode including the two the registry says must unset it — also pre-existing, also now documented.

Verification

  • npm test — 6870 passed, 12 skipped, 0 failed.
  • npm run typecheck, npm run lint, npm run format:check, npm run check:frontend-syntax all clean.
  • The new test bites: reverting the registry entry fails it with expected [...] to include 'export COLORTERM=truecolor'.
  • Called buildClaudeEnv() with NO_COLOR=1 and CLAUDECODE=1 in the parent env: returns COLORTERM=truecolor, no CLAUDECODE, and NO_COLOR preserved.
  • Called the real buildEnvExports against a build: a Claude pane emits unset CLAUDECODE, export COLORTERM=truecolor. OpenCode still emits unset COLORTERM, unchanged.
  • Ran it for a day on a live install, then opened a fresh session on the stock dark theme. The user-message block renders as the intended grey, and nothing shows gray or white where it should not.

🤖 Generated with Claude Code

Claude draws the user's own messages as a block of background color, and
inside a Codeman pane that block was invisible. tmux hands each pane
TERM=screen, which supports-color reads as 16 colors, and Claude's registry
entry deleted COLORTERM on top of that. Claude therefore quantized every RGB
color its theme asked for down to the basic palette, where rgb(55, 55, 55)
and every other dark background becomes ESC[40m, the terminal's own black.
Changing the color in a custom Claude theme moved nothing on screen.

Claude now exports COLORTERM=truecolor and unsets NO_COLOR, matching codex,
gemini, antigravity, pi, grok and omp. CLAUDECODE stays unset, because Claude
reads it as a signal that it is running nested inside itself. Both the tmux
session and the attach client read this one registry entry, so they cannot
disagree.

PR Ark0N#3 introduced the unset in February, citing xterm.js#484 for the claim
that xterm.js mishandles truecolor. xterm.js closed that issue in April 2019,
Codeman now depends on @xterm/xterm 6, and TmuxManager already sets
terminal-overrides ",*:Tc" on its own tmux server, so 24-bit color reaches
the browser today for every CLI that asks for it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…pawn path

buildClaudeEnv(), the direct-PTY fallback taken when mux creation fails, now
reads getCli('claude').env and applies its unset and exports lists. It used to
delete COLORTERM and CLAUDECODE from a hand-maintained list of its own, which
left it contradicting the registry entry that the tmux pane and the attach
client both read. An engine value needing a mux name has nothing to resolve
against on this path, so it is skipped rather than guessed.

Claude no longer unsets NO_COLOR. The invisible-background bug does not need
it, and unsetting it overrides a preference the user set deliberately, so a
user who exports NO_COLOR globally keeps monochrome panes. The other seven
truecolor CLIs still unset it; that inconsistency is intentional and the
comment on the entry says so.

The invariants doc gains a Terminal colour env paragraph under Session launch
modes, where a reader looking up Claude will find it — the previous sentence
sat under a heading that lists only the non-Claude CLIs. It now says the lists
are the stock catalog and a clis.json override replaces them wholesale, and
that the declarations reach the tmux pane, its attach client and the direct
PTY but not a remote pane, whose command carries no env exports at all. Docker
hands COLORTERM=truecolor to every mode, including the two the registry says
must unset it.

The changeset named six peer CLIs and there are seven: deepseek also exports
truecolor. A test beside the existing OpenCode assertion pins the new
behaviour, so a future registry edit cannot make the backgrounds vanish again
in silence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

1 participant