Custom theme `overrides` for diff colors are silently ignored (diff keys resolve against base theme, not merged theme)
Summary
Custom themes (~/.claude/themes/<slug>.json with { "name", "base", "overrides" }) correctly apply overrides for almost every color key, but diff color overrides have no effect. Overriding diffAdded / diffAddedDimmed / diffRemoved / diffRemovedDimmed / diffAddedWord / diffRemovedWord does nothing — the diff always renders with the base theme's diff palette. Non-diff overrides in the same file (e.g. claude, permission, text) apply correctly, so the theme is loading and merging fine; only the diff render path ignores the merged values.
Environment
- Claude Code 2.1.187 (native installer build,
~/.local/share/claude/versions/2.1.187) - Linux (aarch64), tmux + terminal with truecolor
- Theme selected via both
/themeandsettings.json"theme": "custom:<slug>"— same result
Repro
- Create
~/.claude/themes/difftest.json:
``json``
{
"name": "Diff Test",
"base": "dark",
"overrides": {
"claude": "rgb(255,0,255)",
"diffAdded": "rgb(0,0,255)",
"diffRemoved": "rgb(255,128,0)"
}
}
- Restart Claude Code (the themes dir is scanned at startup; a mid-session-created dir/file isn't picked up until restart — minor separate annoyance).
- Select Diff Test in
/theme. - Confirm the
claudeoverride took effect (Claude's accent is now magenta). - Trigger a file-edit diff preview (any
Edit).
Expected: added lines render with a blue background (rgb(0,0,255)), removed lines orange (rgb(255,128,0)).
Actual: added/removed lines render with the base dark theme's green/red backgrounds. The diff* overrides are ignored, even though claude (same file) is honored.
Root cause (from inspecting the bundled binary — pointers, not exact identifiers)
The theme load + merge path is correct: the loader keeps an override when Object.hasOwn(nj(base), key) && isValidColor(value), and the merge ({...nj(base), ...overrides}-style) keeps the diff keys (the base dark object does contain them). So the merged active theme has the right diff values.
The problem is the diff renderer passes the color as a key name string, not a resolved literal:
// diff line renderer
I = i === "add"
? (r ? "diffAddedDimmed" : "diffAdded")
: (r ? "diffRemovedDimmed" : "diffRemoved");
// ...
<R color={o ? "text" : undefined} backgroundColor={I} dimColor={r}> ... </R>
…and the low-level color resolver resolves bare key names against nj(base) (the static base theme), bypassing the merged overrides:
return (value) => {
if (!value) return inherited;
if (value.startsWith("rgb(") || value.startsWith("#")
|| value.startsWith("ansi256(") || value.startsWith("ansi:"))
return apply(inherited, value); // literal → used as-is
return apply(inherited, nj(base)[value]); // KEY NAME → base theme only, no overrides
};
So:
- Accent colors work because components read them from the merged theme context and pass
rgb(...)literals, hitting the literal branch. - Diff colors break because the diff component passes raw key names (
"diffAdded","diffAddedWord", …), which hit thenj(base)[value]branch and never see the overrides.
Affected keys
diffAdded, diffAddedDimmed, diffRemoved, diffRemovedDimmed, diffAddedWord, diffRemovedWord (the last two are passed as color="diffAddedWord" etc., same bypass). More generally: any color passed to the resolver as a key name rather than a resolved literal ignores custom-theme overrides — the diff renderer is the visible case.
Suggested fix
Either:
- Have the diff renderer resolve colors from the merged active theme (
useTheme()) and passrgb(...)literals, like other components; or - Make the low-level key-name resolver resolve against the merged active theme (base + overrides) instead of
nj(base).
The second is the more general fix, since it closes the bypass for any other key-name color sites.
Impact / workaround
No workaround that achieves arbitrary diff colors: the diff palette is effectively locked to the chosen base theme (dark → green/red, dark-daltonized → blue/dark-red, dark-ansi → ANSI green/red). Users who want custom diff colors (e.g. to match a delta/git-pager Dracula scheme) can't, despite the keys existing and validating in the theme file.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗