Hook command dedup ignores env expansion, dropping plugin hooks
Summary
Hooks are deduplicated by the raw command string (pre–env expansion). When multiple plugins use the same command string (e.g. ${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.ts), only one hook runs even though the expanded paths differ per plugin.
Steps to reproduce
- Create two plugins (A and B).
- In each plugin’s
hooks/hooks.json, register a Stop hook with the same command string:
``json``
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/hooks/stop-hook.ts"
}
]
}
]
}
}
- Enable both plugins.
- Trigger a Stop event.
- Inspect debug logs.
Expected
Both hooks execute (the expanded command paths are different per plugin).
Actual
Only one hook runs. Debug shows "Matched 1 unique hooks (2 before deduplication)".
Impact
Cross‑plugin hooks silently conflict, especially for Stop hooks (e.g., review gates, safety checks). This can disable critical behavior without warning.
Suggestions
- Deduplicate after env expansion, or
- Include plugin identity (plugin root/name) in the dedupe key.
- If current behavior is intentional, document the requirement to make hook commands unique (e.g., distinct filenames or args).
This issue has 7 comments on GitHub. Read the full discussion on GitHub ↗