fix(terminal): let Claude use truecolor so its themed backgrounds render - #409
Conversation
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>
#409 (Claude truecolor). The changeset becomes the changelog, and its premise does not hold on tmux 3.2 or newer. Measured here on tmux 3.4: `default-terminal` sits at its compiled default of `tmux-256color`, a live claude pane reports `TERM=tmux-256color`, and supports-color reads that as 256 colors, where rgb(55,55,55) lands on ESC[48;5;237m — visible, just not the color the theme named. The invisible block the PR describes needs TERM to resolve to a 16-color entry: tmux older than 3.2, or a ~/.tmux.conf setting `default-terminal screen`, which Codeman's own tmux server does read (it passes no -f). Both the changeset and the invariants paragraph now say that, so the next report here gets paired with the reporter's tmux -V instead of being read as universal. The change itself stands on the simpler argument: claude was one of two entries not asking for truecolor while twelve do. Also reorders buildClaudeEnv(). It applied the registry's unset/exports AFTER the whole env was built, so a clis.json entry naming CODEMAN_HOOK_SECRET_FILE or PATH would strip it on the direct-PTY path while the tmux pane kept it — buildEnvExports() emits `...cliEnv` ahead of `export CODEMAN_MUX=1` and cannot. The block now runs first and Codeman's own keys are assigned on top, matching the pane. #404 (Ctrl+Z trap). Adds the missing changeset, and records what the trap does not cover: an agent CLI already holds its tty with ISIG off (verified on three live panes: `susp = ^Z -isig -icanon`), so this is defence for the startup window rather than a fix for the steady state, and two input paths still reach the PTY unfiltered — the mobile accessory bar's one-shot Ctrl and the CJK textarea. #399 (path picker sort). The server sorts by name and cuts at 500, so the client sorting those 500 by date gives "the newest of the first 500 by name", which is wrong in exactly the >500-entry folder the date sort exists for. The status line now says "(first 500 by name)" so the cut is legible, with the reasoning parked on _sortEntries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Merged, thank you, and thank you for flagging the reading you could not prove rather than quietly leaving it out. That paragraph is the reason I went and measured instead of taking the table on trust. I verified your colour maths by replicating But the premise does not hold on tmux 3.2 or newer, and that changes the framing rather than the change. Measured on this box: tmux 3.4, The other half is that PR #3's own body says its goal was to "fall back to standard 256-color mode which xterm.js fully supports", which is what actually happens on a modern tmux. So this does not so much reverse #3 as make it obsolete. None of that argues against the change. It argues for a different headline, and since the changeset becomes the changelog I rewrote it in One real fix, in the same commit. Not changed, your call if you want a follow-up: the engine-value resolver is now in two places with different coverage (2 of 7 sources here, 3 of 7 in Unrelated and pre-existing, in case it interests you: the live server has ten duplicate |
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=truecolorfor Claude, which is what the other seven CLIs already do.The problem
Two things combine. tmux hands each pane
TERM=screen, whichsupports-colorreads as 16 colors, and Claude's registry entry deletedCOLORTERMon top of that. Claude then quantizes every RGB color its theme asks for down to the basic palette.Measured with the
chalkversion Claude bundles, at 16 colors:rgb(55, 55, 55)(built-in dark)ESC[40mrgb(28, 48, 84)(a custom theme)ESC[40mrgb(240, 240, 240)(built-in light)ESC[47mEvery dark background lands on
ESC[40m, so the block is emitted and paints the terminal's background over itself. Claude's*-ansithemes work, because they name colors likeansi:blackBrightand skip the conversion entirely.Detected levels, same library:
TERMCOLORTERMscreenscreentruecolorWhy this reverses #3
#3 added
unset COLORTERMin 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/xterm^6.0.0, which renders 24-bit color.TmuxManagersetsterminal-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 exportsCOLORTERM=truecolor.CLAUDECODEstays unset.src/session-cli-builder.ts—buildClaudeEnv(), 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 deletedCOLORTERMfrom 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.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 takeCOLORTERM=truecolorfrom the hardcodedenvCreate/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-syntaxall clean.expected [...] to include 'export COLORTERM=truecolor'.buildClaudeEnv()withNO_COLOR=1andCLAUDECODE=1in the parent env: returnsCOLORTERM=truecolor, noCLAUDECODE, andNO_COLORpreserved.buildEnvExportsagainst a build: a Claude pane emitsunset CLAUDECODE,export COLORTERM=truecolor. OpenCode still emitsunset COLORTERM, unchanged.🤖 Generated with Claude Code