Hook deduplication compares literal strings before variable expansion
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.
This issue has 4 comments on GitHub. Read the full discussion on GitHub ↗