Skip to content

Local plugin hooks match but never execute (isLocal: true) #14410

Description

@Cygnusfear

Summary

Local plugins (isLocal: true) have their hooks matched by Claude Code but the hooks are never actually executed. This appears to be the same root cause as #11509, #11939, and #12151.

Environment

  • Claude Code version: 2.0.64
  • Platform: macOS Darwin 24.6.0
  • Plugin installation: Local (isLocal: true in installed_plugins.json)

Steps to Reproduce

  1. Create a local plugin with hooks in hooks/hooks.json:
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "my-plugin hook queue-synthesis",
            "async": true
          }
        ]
      }
    ]
  }
}
  1. Install the plugin locally (results in isLocal: true)

  2. Run Claude Code and observe debug logs

Expected Behavior

The Stop hook should execute when Claude finishes responding.

Actual Behavior

Debug logs show:

Loaded hooks from standard location for plugin totalrecall: /Users/.../.claude/plugins/cache/totalrecall-local/totalrecall/3.0.5/hooks/hooks.json
Loading hooks from plugin: totalrecall
Getting matching hook commands for Stop with query: undefined
Found 1 hook matchers in settings
Matched 1 unique hooks for query "no match query" (1 before deduplication)

But no execution follows. The command is never run. No process spawned. Manual execution of the same command works perfectly:

echo '{"session_id":"test","transcript_path":"/path/to/transcript.jsonl"}' | my-plugin hook queue-synthesis
# Returns: {"queued":true,"queue_id":1,"message":"Session chunk queued for synthesis"}

Workaround

Moving hooks from the plugin's hooks/hooks.json to the user's ~/.claude/settings.json works:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "/absolute/path/to/my-plugin hook queue-synthesis",
            "timeout": 30000
          }
        ]
      }
    ]
  }
}

This confirms the hook configuration is correct - only the plugin hook execution path is broken.

Evidence from installed_plugins.json

"my-plugin@my-plugin-local": {
  "isLocal": true
}

Related Issues

Impact

This bug completely breaks the hook functionality for any locally-developed plugin, forcing developers to either:

  1. Ask users to manually copy hooks to their settings.json
  2. Publish to git and install from remote (changes isLocal to false)

Neither is acceptable for local plugin development workflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:corebugSomething isn't workinghas reproHas detailed reproduction stepsplatform:macosIssue specifically occurs on macOSstaleIssue is inactive

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions