Custom themes: effortUltra (ultracode/effort status badge) ignores theme overrides — always renders built-in purple

Status Open
Reported on v2.1.186
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Jun 23, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Summary

Custom themes (~/.claude/themes/<slug>.json referenced via "theme": "custom:<slug>") apply their overrides correctly everywhere in the UI except the effort / ultracode status badge in the bottom-right of the input box.

That badge always renders the built-in base palette color for effortUltra (for a dark-based theme, the hardcoded rgb(135,0,255) purple), regardless of any effortUltra value in the custom theme. No override value and no restart changes it.

Environment

  • Claude Code v2.1.186 (native install)
  • macOS (Darwin 25.3.0)
  • "theme": "custom:minimal", custom theme with "base": "dark"

Repro

  1. Create ~/.claude/themes/mine.json:

``json
{ "name": "Mine", "base": "dark", "overrides": { "effortUltra": "rgb(200,200,200)" } }
``

  1. Set "theme": "custom:mine" in ~/.claude/settings.json.
  2. Enable ultracode ("ultracode": true) so the effort badge is shown.
  3. Restart Claude Code and look at the bottom-right ultracode badge.

Expected: badge text is gray rgb(200,200,200).
Actual: badge text is the built-in purple rgb(135,0,255). The override is silently ignored, while every other override in the same file applies correctly (the rest of the UI is fully grayscale).

Root cause

Function names below are minified/build-specific to 2.1.186 — the logic is the point.

The badge is rendered (function Aml) by, in effect:

let t = G3(getConfig("theme","dark").value);   // t = resolved theme for "custom:minimal"
return Co("effortUltra", t)("ultracode");        // colorize the text "ultracode"
  • G3("custom:minimal") ends up returning the base name string "dark". The custom-theme registry (mEn) stores slug → base only (mEn.set(t.slug, t.base)), and sAi(slug) returns that base string — so the resolver yields "dark", not the merged theme.
  • Co("effortUltra", "dark") resolves the key via rz("dark")["effortUltra"], where rz() returns the raw built-in dark palette (effortUltra: "rgb(135,0,255)"). It never consults the theme's overrides.

By contrast, the rest of the UI resolves colors through the merged palette built by tAi(rz(base), theme.overrides) (e.g. the React useMemo around the theme provider), which is why all other overrides apply. The effort/ultracode badge path bypasses that merge.

Consequence for any custom: theme:

  • base: "dark" → badge stuck on rgb(135,0,255)
  • base: "dark-daltonized" → stuck on rgb(175,135,255)
  • 16/256-color terminals → stuck on ansi:magenta / ansi:magentaBright

In every case the user's effortUltra override is ignored.

Suggested fix

Resolve the badge color against the merged theme palette (base + overrides) — the same palette the rest of the UI uses — rather than rz(resolveTheme(...)). Equivalently, pass the resolved/merged theme object (not the base-name string) into the color lookup for the effort/ultracode badge in Aml.

Impact

Cosmetic, but it makes grayscale / monochrome and accessibility-oriented custom themes impossible to finish: the always-purple effort badge can't be neutralized by any theme setting.

View original on GitHub ↗

3 Comments

ssud11 · 3 months ago

Another confirmed instance of custom-theme overrides being ignored, on a different token path: inline code (markdown codespan) always renders the built-in base theme's permission color, regardless of the custom theme's override.

Repro (v2.1.207, Windows, custom theme set at launch — not a mid-session switch):

Launched with claude --settings '{"theme":"custom:papyrus-lamplight"}' where the theme overrides permission: #E8A878 (warm apricot), then captured the raw escape output from the pane. Same message, two tokens, two different sources:

  • userMessageBackground override → honored (48;5;52, the theme's value)
  • inline-code span → 38;5;153 = the built-in dark theme's permission rgb(153,204,255) quantized — the custom override (#E8A878, which would be slot 216) is never consulted

In the shipped bundle the codespan renderer resolves as case "codespan": return Jn("permission", t)(...) — evidently against the base theme rather than the merged custom theme, while other render paths (message backgrounds, prompt borders, spinner) read the merged theme correctly.

So the failure class in this issue isn't specific to effortUltra — at least codespan behaves the same way, and #57036 (diff overrides) looks like a third sibling. Practical impact: any warm/no-blue custom theme still shows blue inline code everywhere.

(Color-depth context, separate issue: #77032 — the values above are 256-indexed because of the VS Code clamp, but the wrong-source lookup reproduces independently of color depth.)

bcherny collaborator · 1 month ago

Confirmed — reproduced on v2.1.233 (Linux).

I created ~/.claude/themes/mine.json with "base": "dark" and overrides for effortUltra (gray) plus two control keys (promptBorder red, inactive green), set "theme": "custom:mine" and "ultracode": true, and launched Claude Code in a terminal that lets me inspect the emitted color codes.

The control overrides applied immediately (red prompt border, green inactive text), proving the custom theme loads — but the ultracode badge on the input-box border still rendered the built-in purple, and the gray override color never appeared anywhere in the output. Restarting doesn't help.

Verdict: this is a real bug. The effort/ultracode badge resolves its color from the base palette and skips the theme-override merge that the rest of the UI uses, so an effortUltra override is silently ignored (and since that badge is the only place the color is used, the key is effectively un-overridable). Marking as reproduced; a fix is to route the badge through the same merged palette as everything else.

🤖 Generated with Claude Code

marcindulak · 27 days ago

For your information: the label reproduced on this issue does not prevent it from becoming stale and eventually auto-closed. See https://github.com/anthropics/claude-code/issues/87647 for details, and consider upvoting that for visibility.