[BUG] Custom theme overrides silently ignored for markdown-rendered slots (codespan, text, error, warning, success, ...)
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Summary
Custom themes in ~/.claude/themes/<name>.json accept an overrides map, and the theme loader validates and stores it. However, the markdown renderer bypasses the merged theme and reads colors straight from the hardcoded base palette, so
overrides for any slot touched by the renderer are silently ignored.
Most visible symptom: inline code spans (` like this ) always render with the base permission color (light periwinkle rgb(177,185,249) on base: "dark"), regardless of what the user sets for permission` in overrides.
Version
- Claude Code: 2.1.118 (also reproduces on 2.1.117)
- Platform: macOS 14 (Kitty + tmux + sidekick.nvim, but also reproduces bare in Warp)
Suggested fix
Thread the resolved theme (or at minimum the merged palette K99(bx(base), overrides)) through the markdown renderer entry point, and have a8 read from that merged palette rather than bx(baseName). The existing L computed for React would work —
it just needs to be passed into the markdown renderer so every a8(slot, _) resolves against the merged palette.
Workaround
None via the theme file — every affected slot is hardcoded per base. The only partial mitigation is switching base: "dark" → base: "dark-ansi", which makes the renderer pull ansi:blueBright etc. from the terminal's ANSI palette. If you already
run a tokyonight/solarized/etc. terminal palette it'll look coherent, but you still can't tweak individual slots from the theme file.
What Should Happen?
Expected: inline code renders pure red; warning/success/error tokens render the overridden colors.
Actual: all of the above render in the base "dark" palette's colors, as if overrides were empty. background, bashBorder, diffAdded*, userMessageBackground*, mode badges, and other chrome slots do honor overrides correctly — so
it's not a file-loading issue.
Error Messages/Logs
Steps to Reproduce
Reproduction
- Create
~/.claude/themes/test.json:
``json``
{
"name": "test",
"base": "dark",
"overrides": {
"permission": "#ff0000",
"text": "#ff0000",
"warning": "#ff0000",
"success": "#ff0000",
"error": "#00ff00"
}
}
/theme→ select "test" → reopen session.- Ask Claude to produce a reply containing
inline code, awarningmessage, etc.
Expected: inline code renders pure red; warning/success/error tokens render the overridden colors.
Actual: all of the above render in the base "dark" palette's colors, as if overrides were empty. background, bashBorder, diffAdded*, userMessageBackground*, mode badges, and other chrome slots do honor overrides correctly — so
it's not a file-loading issue.
Root cause
The theme loader correctly builds the validated overrides dict A and keeps it in the theme object:
// theme loader
if (typeof O.overrides === "object" && O.overrides !== null) {
let z = bx(T);
for (let [Y, w] of Object.entries(O.overrides))
if (Object.hasOwn(z, Y) && UDH(w)) A[Y] = w;
}
return { slug: H, name: $, base: T, overrides: A, source: q };
React components consume a merged theme:
L = wD.useMemo(() => K99(bx(G), A ?? W?.overrides), [G, A, W]);
But the markdown renderer calls the color helper a8 with the base-name string, not the merged theme:
// markdown renderer
case "codespan": return a8("permission", _)(H.text);
// ^^^^^^^^^^^^^ _ is the base name ("dark"), not the merged theme
And a8 itself looks up the value from the hardcoded base palette only:
function a8(H, _, q = "foreground") {
return (K) => {
if (!H) return K;
if (H.startsWith("rgb(") || H.startsWith("#") || H.startsWith("ansi256(") || H.startsWith("ansi:"))
return LKH(K, H, q);
return LKH(K, bx(_)[H], q); // ← bx(baseName)[slot] — merged overrides never consulted
};
}
function bx(H) {
switch (H) {
case "light": return va4;
case "light-ansi": return Na4;
case "dark-ansi": return Va4;
case "light-daltonized": return ha4;
case "dark-daltonized": return Sa4;
default: return ya4; // "dark"
}
}
So the merged overrides A are built but never threaded into the markdown renderer's color lookup path. All a8(slot, baseName) call-sites silently fall through to the base palette.
Affected slots
Every slot looked up via a8(slot, ...) in the markdown renderer. Grep of the binary finds:
- claude
- error
- fastMode
- inactive
- permission (← used for codespan / inline code)
- promptBorder
- success
- suggestion
- text
- warning
Slots applied through React props (L/K99) — e.g. background, bashBorder, userMessageBackground, diffAdded, rate_limit_, clawd_, mode badges, subagent colors — are unaffected and honor overrides correctly.
Claude Model
Opus
Is this a regression?
No, this never worked
Last Working Version
_No response_
Claude Code Version
2.1.118 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
Other
Additional Information
_No response_
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗