`$.config.list()` / `$.config.set` have no plugin `userConfig` rows until `/reload-plugins` in desktop-hosted and headless sessions
Environment
- Claude Code 2.1.295 (macOS, arm64)
- Claude desktop app 2.26454.2 (Code tab); also reproduces with
claude -p
Summary
According to the plugin API docs, each enabled plugin's non-sensitive userConfig fields appear as /config rows keyed <plugin>.<field>, and a plugin can change them with $.config.set. In sessions hosted by the desktop app, and in headless claude -p sessions, those rows are missing until something refreshes plugins. The plugin's hooks load and run normally, but:
$.config.list()returns only the engine's own rows. Plugin rows are missing, including the built-incc-plugin-agents-md.instructionFiles.$.config.set({ key: '<plugin>.<field>', value })throwsno /config row with key <plugin>.<field> ($.config.list names them).
After /reload-plugins in the same session, the rows appear and $.config.set works.
The rows appear to come from getAppState().plugins.enabled. That list is filled in by the interactive initial plugin load and by applyRefresh, and is apparently left empty in SDK-hosted and headless sessions until a refresh. The hook modules load through a separate path, so a plugin is running while its own rows don't exist.
Reproduction
- Install any plugin with a
userConfigfield, for example a fieldcompactLabelson a plugin namedstatusline. - Create a diagnostic plugin folder
config-rows:
hooks/register.ts:
``ts``
import type { Register } from 'claude-code'
const pluginRows = async ($) => (await $.config.list()).map((row) => row.key).filter((key) => key.includes('.')).join(', ')
export const register: Register = (on) => {
on('session.start', async ($, e, next) => {
await $.command.register({ name: 'configrows', description: 'List plugin /config rows' })
return next(e)
})
on('command.run', { command: 'configrows' }, async ($) => ({ text: 'ROWS: ' + await pluginRows($) }))
}
- Run
claude -p --plugin-dir ./config-rows "/configrows". The output isROWS:(empty). - Send
/reload-pluginsand then/configrowsin one session:
``sh`
printf '%s\n' \
'{"type":"user","message":{"role":"user","content":"/reload-plugins"}}' \
'{"type":"user","message":{"role":"user","content":"/configrows"}}' \
| claude -p --plugin-dir ./config-rows --input-format stream-json --output-format stream-json --verbose
statusline.colorMode, …, statusline.compactLabels, cc-plugin-agents-md.instructionFiles`.
The output now lists
In a desktop app Code-tab session, the same thing happens without -p: /configrows (or a plugin command that calls $.config.set) finds no plugin rows until /reload-plugins runs or something else refreshes plugins, such as enabling mod hot-reloading.
Expected
Plugin userConfig rows are present in $.config.list() and accepted by $.config.set whenever the plugin's hooks are loaded, on every surface, without a manual /reload-plugins.
Impact
Any plugin command that changes its own options through $.config.set fails in desktop sessions. Plugins have no API to trigger a refresh themselves, so the only workaround is to tell people to run /reload-plugins or edit ~/.claude/settings.json by hand.