[BUG] Mods: $.config.set({ key: 'theme' }) returns success but the running session's theme and $.config.list() don't change
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
A plugin calling $.config.set({ key: 'theme', value }) gets back { value } with no deny, and ~/.claude/settings.json is rewritten with the new value. But nothing changes in the running session:
- the rendered palette stays the same (including
Codeelements in a pluginPane, bothformat: 'diff'and plain source) $.config.list()called right after the set, in the same session, still reports the old value for thethemerow
The plugin API docs say $.config.set "changes one row as if the person did in the menu". Changing Theme in the /config menu applies right away. So from a plugin's side, the call looks successful but does nothing until restart.
Possibly the same root cause as #98161 (theme in settings.json not applied to a running session). This report covers the plugin API path: the API reports success, and its own list() contradicts the set.
Environment
- Claude Code 2.1.300
- macOS (Darwin 24.6.0)
- Terminal: IntelliJ IDEA built-in terminal (IntelliJ IDEA 2026.x.x, TERM=xterm-256color)
- Reproduced in an interactive session and in
claude -p
Reproduction
Minimal plugin, hooks/register.ts:
import type { Register } from 'claude-code'
export const register: Register = on => {
on('session.start', async ($, e, next) => {
const theme = async () => (await $.config.list()).find(r => r.key === 'theme')?.value
const log: unknown[] = [['before', await theme()]]
log.push(['set dark', await $.config.set({ key: 'theme', value: 'dark' })])
log.push(['after', await theme()])
console.error(JSON.stringify(log))
return next(e)
})
}
Start with "theme": "light" in settings, then run claude --plugin-dir <dir>.
Actual
[["before","light"],["set dark",{"value":"dark"}],["after","light"]]
~/.claude/settings.jsonnow contains"theme": "dark"- The session keeps the light palette, and
list()still sayslight
In an interactive session, a plugin Pane button that toggles the theme this way updates its own label, but the diff colors in the pane never change.
Expected
One of:
- The theme applies to the running session, the same as picking it in
/config, andlist()reflects it; or - If the theme can't be changed live from a plugin,
$.config.setreturns{ deny: '...' }instead of reporting success.
Why it matters
The plugin API leaves all Code colors to the engine ("the colour on screen is the engine's, never the plugin's"). Changing the theme is the only way a plugin can offer a light/dark switch for diffs it renders.
What Should Happen?
$.config.set({ key: 'theme', value }) from a plugin should behave like picking the theme in the /config menu: the running session repaints with the new theme immediately (including Code elements in plugin Panes), and $.config.list() reports the new value right after the call.
If changing the theme from a plugin isn't supported in a running session, $.config.set should return { deny: '<reason>' } instead of { value }, so the plugin can tell the user rather than silently doing nothing.
Error Messages/Logs
Steps to Reproduce
- Set the theme to light with
/config→ Theme →light, so~/.claude/settings.jsonhas"theme": "light". - Create a minimal plugin folder
theme-repro/with these three files:
theme-repro/.claude-plugin/plugin.json
``json``
{ "name": "theme-repro", "version": "0.0.1" }
theme-repro/hooks/hooks.json
``json``
{ "modules": ["./register.ts"] }
theme-repro/hooks/register.ts
```ts
import type { Register } from 'claude-code'
export const register: Register = on => {
on('session.start', async ($, e, next) => {
const theme = async () => (await $.config.list()).find(row => row.key === 'theme')?.value
const log: unknown[] = [['before', await theme()]]
log.push(['set dark', await $.config.set({ key: 'theme', value: 'dark' })])
log.push(['after', await theme()])
await $.process.run(['sh', '-c', 'printf %s "$1" > /tmp/theme-repro.json', 'sh', JSON.stringify(log)])
return next(e)
})
}
```
- Run
claude --plugin-dir ./theme-repro(interactive).claude -p "say ok" --plugin-dir ./theme-reproshows the same log. - Check the log:
cat /tmp/theme-repro.json
```
[["before","light"],["set dark",{"value":"dark"}],["after","light"]]
set
reports success, but list() still returns light` right after it.
- Check the settings file:
grep '"theme"' ~/.claude/settings.jsonnow shows"theme": "dark". - Look at the interactive session from step 3: it is still drawn with the light palette. Code blocks and diffs keep their light colors for the rest of the session. The dark theme only appears after restarting Claude Code.
- For comparison, change Theme in
/configby hand: the session repaints immediately.
Claude Model
_No response_
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.300 (Claude Code)
Platform
Anthropic API
Operating System
macOS
Terminal/Shell
IntelliJ IDEA terminal
Additional Information
_No response_