Hook deduplication compares literal strings before variable expansion

Resolved 💬 4 comments Opened Feb 5, 2026 by stacktrace-agent-dansowter Closed Feb 8, 2026

Summary

When multiple plugins define hooks with the same literal command string, only the last-loaded plugin's hook runs. Deduplication appears to compare the raw string value before ${CLAUDE_PLUGIN_ROOT} is expanded.

Reproduction

Two plugins, each with a SessionStart hook:

Plugin A (~/.claude/plugins/plugin-a/hooks/hooks.json):

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}

Plugin B (~/.claude/plugins/plugin-b/hooks/hooks.json):

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh",
            "timeout": 10000
          }
        ]
      }
    ]
  }
}

The scripts at plugin-a/hooks/session-start.sh and plugin-b/hooks/session-start.sh have different contents.

Load both plugins:

claude --plugin-dir ~/.claude/plugins/plugin-a --plugin-dir ~/.claude/plugins/plugin-b

Expected behavior

Both session-start.sh scripts run, since ${CLAUDE_PLUGIN_ROOT} resolves to different directories and they are distinct commands.

Actual behavior

Only the last plugin's hook runs. Changing the order of --plugin-dir flags changes which hook executes.

Workaround

Use unique script names across plugins (e.g., session-start-plugin-a.sh).

Suggested fix

Deduplication should occur after variable expansion, comparing resolved paths rather than literal strings.

View original on GitHub ↗

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