Plugin autoUpdate doesn't update installed_plugins.json, leaving bundled hooks pinned to stale installPath
Summary
When a plugin marketplace has "autoUpdate": true in ~/.claude/plugins/known_marketplaces.json, Claude Code promotes the plugin's runtime version at process start (shown in /plugin UI, used for skills/commands) — but does not update ~/.claude/plugins/installed_plugins.json. Because plugin-bundled hooks load from the installPath recorded in that file, autoUpdate can never deliver a new bundled hook to users; they're silently pinned to whatever version was last explicitly installed.
A second ordering issue compounds this: autoUpdate appears to run before SessionStart hooks during process startup, so a hook that git pulls the marketplace cache only primes the cache for the next relaunch. Each plugin release ends up needing two relaunches to fully propagate.
Environment
- Claude Code: v2.1.117
- Platform: macOS (Darwin 25.2.0)
- Plugin: custom enterprise plugin bundling
hooks/hooks.json(SessionStart, matcher-wrapper schema)
Reproduction
- Create a plugin with
"autoUpdate": truein its marketplace entry and a bundledhooks/hooks.jsonwhose SessionStart command writes a timestamp to a log file (so you can observe firings). /plugin install <name>→installed_plugins.jsonrecordsversion: A,installPath: .../A/.- Push version B to the plugin repo with a clearly different hook body.
- Fully quit and relaunch Claude Code (no manual
/plugin marketplace updateor/plugin update). - Observe:
/pluginUI shows version B (autoUpdate advanced skills/commands) ✅installed_plugins.jsonstill says A (installPathstill.../A/) ❌$CLAUDE_PLUGIN_ROOTinside the hook log resolves to.../A/— i.e., A's hook is still firing, not B's.
Expected behavior
When autoUpdate promotes a plugin to a newer version, installed_plugins.json should also be rewritten so version and installPath reflect the newer version. Bundled hooks should load from the new installPath.
Actual behavior
installed_plugins.json is only updated by explicit user actions:
/plugin install/plugin update/pluginUI → Update now
autoUpdate hot-loads newer skills/commands into the running process but leaves installed_plugins.json untouched, pinning bundled hooks to the last-explicitly-installed version.
Impact
- Bundled hook updates never reach users via
autoUpdate. Plugin authors who want to iterate on their hook must instruct every user to click Update now manually for each hook change. - Every release takes two relaunches to propagate, even when the hook logic is stable, because
autoUpdateruns before the SessionStart hook during process start. The hook's fetch only primes the cache for the next relaunch'sautoUpdate. - For plugins with simple stable hooks (e.g., a boring
git pull) this is a latent gotcha rather than a breaking bug. For plugins that want to evolve their hooks, it's a blocker.
Suggested fixes
Either or both would resolve this:
- Persist autoUpdate results. When
autoUpdateloads a newer version at process start, atomically rewriteinstalled_plugins.json(version + installPath), and subsequently load bundled hooks from the newinstallPath. - Reorder startup. Run SessionStart hooks before the
autoUpdateversion-resolution step, so a hook that primes the marketplace cache can influence the same relaunch'sautoUpdatedecision.
Workaround (current)
Users must run /plugin UI → Update now once after any plugin release that changes the bundled hook. After that, simpler bundled hooks (e.g., git pull to keep the marketplace cache fresh) keep working across subsequent autoUpdate cycles as long as the hook command itself doesn't need to evolve.
This issue has 6 comments on GitHub. Read the full discussion on GitHub ↗