Disabled plugin hooks still fire (claude-preview Stop hook)
Summary
When the claude-preview plugin is disabled via enabledPlugins: { "claude-preview@claude-code-marketplace": false }, its Stop hook still fires on session end, injecting a [Preview Required] Code was edited but no dev server is running message. This affects non-web projects (CLI tools, npm packages, Tauri apps, backend services) that will never have a browser dev server.
Environment
- Claude Code version: Latest (as of 2026-03-19)
- OS: Windows 11 Home 10.0.26300
- Plugin:
claude-preview@claude-code-marketplace - Project type: npm CLI package (
@mcptoolshop/registry-sync)
Settings Configuration
The plugin is disabled at three levels, all ignored:
User settings (~/.claude/settings.json):
{
"enabledPlugins": {
"claude-preview@claude-code-marketplace": false
}
}
Workspace local settings (F:\AI\.claude\settings.local.json):
{
"enabledPlugins": {
"claude-preview@claude-code-marketplace": false
}
}
Project local settings (F:\AI\registry-sync\.claude\settings.local.json):
{
"enabledPlugins": {
"claude-preview@claude-code-marketplace": false
}
}
The hooks dict in all settings files is empty {}. No user-defined Stop hooks exist.
Steps to Reproduce
- Install
claude-previewplugin - Disable it in
~/.claude/settings.jsonwith"enabledPlugins": { "claude-preview@claude-code-marketplace": false } - Open Claude Code in a non-web project (CLI tool, npm package, etc.)
- Ask Claude to edit any source file
- Let session end / stop
Expected: No Stop hook fires from the disabled plugin.
Actual: Stop hook fires with:
[Preview Required] Code was edited but no dev server is running.
Install dependencies if needed, call preview_start, then follow <verification_workflow>.
Impact
- Fires on every session stop after edits, creating persistent noise
- In some cases acts as a blocking callback, interrupting workflow
- No workaround short of
disableAllHooks: true(which is overly broad) - Affects all non-web projects in a workspace that contains mixed project types
Related Issues
- #19893 — Disabled plugin SessionStart hook still fires (same root cause: hook-loading ignores
enabledPlugins: false) - #25086 —
enabledPluginsinsettings.local.jsonsilently dropped if no matching key exists insettings.json - #1288 — Stop hook
{"continue": true}causes infinite loop (documents that the naive workaround backfires)
Additional Notes
- A Stop hook outputting
{"continue": true}was attempted as a workaround but causes an infinite re-invocation loop per #1288 disableAllHooks: trueworks but disables all hooks including managed ones (documented bug — bypasses managed hook precedence)- The plugin's Stop hook appears to load from the plugin manifest itself, not from the user's
hooksconfig, which explains why the hooks dict is empty yet the hook fires - The fix should ensure that
enabledPlugins: falseprevents all plugin hooks from loading, not just the plugin's tools/UI
Suggested Fix
During hook loading, check the merged enabledPlugins state before registering any hooks from a plugin's manifest. If enabledPlugins[pluginId] === false in the resolved settings, skip all hook registration for that plugin.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗