Disabled plugin hooks still fire (claude-preview Stop hook)

Resolved 💬 3 comments Opened Mar 20, 2026 by mcp-tool-shop Closed Mar 23, 2026

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

  1. Install claude-preview plugin
  2. Disable it in ~/.claude/settings.json with "enabledPlugins": { "claude-preview@claude-code-marketplace": false }
  3. Open Claude Code in a non-web project (CLI tool, npm package, etc.)
  4. Ask Claude to edit any source file
  5. 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 — enabledPlugins in settings.local.json silently dropped if no matching key exists in settings.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: true works 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 hooks config, which explains why the hooks dict is empty yet the hook fires
  • The fix should ensure that enabledPlugins: false prevents 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.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗