pluginConfigs lookup for --plugin-dir-loaded plugins doesn't resolve to their marketplace-keyed userConfig

Status Open
Maintainer reply None cached
Activity 0 comments · opened Aug 30, 2026

Problem

When a plugin is loaded via claude --plugin-dir <path> instead of through a marketplace, hook/MCP ${user_config.<key>} template substitution fails with:

Hook failed to run (Stop): Plugin option "autoregen" isn't set. Open /plugin manage to configure it, or check that the plugin's userConfig schema declares "autoregen".

This happens even when the same plugin, installed through a marketplace, already has that userConfig key set to a real value — and the plugin's userConfig schema is well-formed (verified with a valid default and no typos).

Root cause hypothesis

~/.claude/settings.json's pluginConfigs stores marketplace installs keyed as <name>@<marketplace> (e.g. claude-prospector@glitchwerks). A plugin loaded via --plugin-dir instead registers under the bare name (claude-prospector, no @marketplace suffix). The ${user_config.*} substitution used by hook/MCP definitions appears to look up the config under whichever plugin identity the harness resolved for the running instance — under --plugin-dir that's the bare name, which has no corresponding pluginConfigs entry, so the lookup misses even though a value exists under the marketplace-keyed identity for the same plugin content.

Corroborating observation: /plugin (list) shows the --plugin-dir-loaded plugin as "inline" but visually linked back to the marketplace entry (same displayed name/version), and /plugin reconfigure <name> inside a --plugin-dir session does not surface the marketplace install's already-configured fields at all. This suggests display-identity unification and config-key resolution are two separate code paths that disagree: the UI treats the --plugin-dir load as "the same plugin" for display purposes, but the userConfig storage/lookup key is not unified across the two identities.

Repro

  1. Install a plugin through a marketplace and set a userConfig value for it (e.g. a boolean flag consumed by a hook via ${user_config.<key>}).
  2. Confirm the hook works correctly under the marketplace install.
  3. Load the same plugin source via claude --plugin-dir <local-path-to-plugin> in a fresh session (bypassing the marketplace entirely).
  4. Trigger the hook that reads ${user_config.<key>}.

Expected: the hook resolves the configured value (or the schema's declared default).
Actual: Hook failed to run: Plugin option "<key>" isn't set, even though the schema is valid and a value is set under the marketplace-keyed install.

Related but distinct: #86936

#86936 ("lspServers ${user_config.*} interpolation ignores declared userConfig defaults") is adjacent — same user-facing error string (Plugin option "X" isn't set) — but a different mechanism: that issue is about the LSP code path not merging the schema's declared default before interpolation (unlike the MCP path, which does merge defaults). This report is about identity/key resolution across install sources (--plugin-dir vs marketplace) for the same plugin, independent of whether a default is declared. A plugin with an explicit value already set under its marketplace identity still fails when the identical source is loaded via --plugin-dir, which #86936's default-merging fix would not address.

Ask

  • Confirm whether pluginConfigs lookup for --plugin-dir-loaded plugins should fall back to (or merge with) a matching marketplace-keyed entry for the same plugin, or whether --plugin-dir sessions need their own documented way to seed userConfig (e.g. does /plugin reconfigure support this today, and if not, should it?).
  • Not blocking for real installs — --plugin-dir is a local-dev-only path; the supported install path is the marketplace. This affects local plugin development/testing workflows.

Origin

Originally reported and investigated in glitchwerks/claude-prospector#277 while verifying that repo's local-dev --plugin-dir workflow (issue #102 / PR #276).

🤖 _Generated by Claude Code on behalf of @cbeaulieu-gt_

View original on GitHub ↗