Hook command dedup ignores env expansion, dropping plugin hooks

Status Closed — not planned
Maintainer reply None cached
Activity 7 comments · opened Jan 8, 2026 · closed Feb 22, 2026

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

  1. Create two plugins (A and B).
  2. 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"
}
]
}
]
}
}
``

  1. Enable both plugins.
  2. Trigger a Stop event.
  3. 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).

View original on GitHub ↗

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