[BUG] SessionStart hooks incorrectly deduplicated across plugins

Resolved 💬 2 comments Opened Jan 29, 2026 by AndaAndaman Closed Jan 30, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

When multiple plugins define SessionStart hooks with the same command pattern (e.g., python "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py"), Claude Code incorrectly deduplicates them down to a single hook
execution.

Debug evidence shows: "Matched 1 unique hooks for query 'startup' (2 before deduplication)"

This means only ONE SessionStart hook runs, even though both plugins are enabled and both hooks are registered. The deduplication happens before ${CLAUDE_PLUGIN_ROOT} variable expansion, causing hooks from
different plugins with identical command strings to be treated as duplicates.

Expected: Both hooks should execute and their additionalContext values should be concatenated (per official documentation).

Actual: Only one hook executes, the other is silently dropped.

What Should Happen?

According to the official documentation at https://code.claude.com/docs/en/hooks#sessionstart-decision-control:

"Multiple hooks' additionalContext values are concatenated."

Expected behavior:

  1. Both SessionStart hooks should execute in parallel
  2. Each hook's hookSpecificOutput.additionalContext should be collected
  3. The outputs should be concatenated together
  4. Both plugin messages should appear at session startup

Example:

  • ask-before-code hook outputs: "📋 ask-before-code Plugin Active..."
  • quick-wins hook outputs: "🎯 quick-wins Plugin Active..."
  • User should see BOTH messages concatenated at startup

The ${CLAUDE_PLUGIN_ROOT} variable should be expanded BEFORE deduplication checking, so that:

  • "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py" from ask-before-code
  • "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py" from quick-wins

Error Messages/Logs

No error messages shown to user, but debug logs reveal the issue:

  Key debug log lines from claude --debug output:

  Line 58: Loaded hooks from standard location for plugin ask-before-code: C:\Users\{username}\.claude\plugins\cache\flowaccount-dev-tools\ask-before-code\0.3.2\hooks\hooks.json

  Line 76: Loaded hooks from standard location for plugin quick-wins: C:\Users\{username}\.claude\plugins\cache\flowaccount-dev-tools\quick-wins\0.3.2\hooks\hooks.json

  Line 84: Registered 3 hooks from 7 plugins

  Line 134-136 (THE SMOKING GUN):
  Getting matching hook commands for SessionStart with query: startup
  Found 2 hook matchers in settings
  Matched 1 unique hooks for query "startup" (2 before deduplication)

  Line 169-171: Only quick-wins hook output appears:
  Hooks: Checking initial response for async: {"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "🎯 **quick-wins Plugin Active**...

  Result: ask-before-code hook never executes, no error message shown.

Steps to Reproduce

Reproduction repository: https://github.com/AndaAndaman/claude-plugins

Setup:

  1. Clone the repository and checkout the bug version:

```bash
git clone https://github.com/AndaAndaman/claude-plugins
cd claude-plugins
git checkout v0.3.2

  1. At v0.3.2, both plugins have identical SessionStart hook commands:
  1. ask-before-code/hooks/hooks.json:

{
"SessionStart": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "python \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py\"",
"timeout": 5
}]
}]
}

  1. quick-wins/hooks/hooks.json:

{
"SessionStart": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "python \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py\"",
"timeout": 5
}]
}]
}

  1. Install both plugins (add to marketplace or enable in settings)
  2. Start Claude Code with debug logging:

claude --debug 2>&1 | tee debug.log

Observe the bug:

  1. Check debug.log around line 134-136:

Getting matching hook commands for SessionStart with query: startup
Found 2 hook matchers in settings
Matched 1 unique hooks for query "startup" (2 before deduplication)

  1. Only ONE SessionStart message appears at startup (either ask-before-code OR quick-wins, not both)

Expected: Both messages concatenated
Actual: Only one message, other hook silently dropped

Workaround: v0.3.3 uses unique script names (session-start-ask-before-code.py, session-start-quick-wins.py)

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

_No response_

Claude Code Version

2.1.23

Platform

Anthropic API

Operating System

Windows

Terminal/Shell

PowerShell

Additional Information

## Root Cause Analysis

The deduplication logic compares raw command strings BEFORE variable expansion:

Before expansion (treated as duplicates):

  • ask-before-code: python "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py"
  • quick-wins: python "${CLAUDE_PLUGIN_ROOT}/hooks/session-start.py"

After expansion (actually different files):

  • ask-before-code: C:\Users\{user}\.claude\plugins\cache\flowaccount-dev-tools\ask-before-code\0.3.2\hooks\session-start.py
  • quick-wins: C:\Users\{user}\.claude\plugins\cache\flowaccount-dev-tools\quick-wins\0.3.2\hooks\session-start.py

## Impact

  1. Plugin developers cannot use standard naming conventions (e.g., session-start.py)
  2. Documentation claims "multiple hooks' additionalContext values are concatenated" but this doesn't work
  3. No error/warning shown to users when deduplication occurs
  4. Likely affects other hook types (Stop, PreToolUse, etc.) with same pattern

## Proposed Fixes

Option 1 (Preferred): Perform deduplication AFTER ${CLAUDE_PLUGIN_ROOT} and other variable expansions

Option 2: Include plugin context in uniqueness check (hooks from different plugins never considered duplicates)

Option 3: Document the limitation clearly and recommend unique script names per plugin

## Workaround Implementation

v0.3.3 uses unique script names per plugin:

  • session-start-ask-before-code.py
  • session-start-quick-wins.py

This prevents deduplication but shouldn't be necessary.

## Related Documentation

Official docs claim concatenation: https://code.claude.com/docs/en/hooks#sessionstart-decision-control
> "Multiple hooks' additionalContext values are concatenated."

## Repository Links

View original on GitHub ↗

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