[BUG] Plugin-registered hooks are executed twice with different PIDs

Status Closed — not planned
Maintainer reply ✓ Yes — dicksontsai
Activity 13 comments · opened Nov 2, 2025 · closed Jan 30, 2026
💡 Likely answer: A maintainer (dicksontsai, collaborator) responded on this thread — see the highlighted reply below.

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 hooks are registered via plugins, they are executed twice with different process IDs. This affects all hook types including SessionStart, Notification, and PreCompact.

Key symptoms:

  • Each hook execution spawns two separate processes (different PIDs)
  • The /hooks command shows duplicate hook registrations (e.g., "2 hooks" when only 1 is defined)
  • This appears to have been occurring since the plugin system was implemented
  • Both executions actually run (verified via log files with different PIDs)

This is NOT a display-only issue - the hooks are genuinely executed twice, causing:

  • Duplicate notifications
  • Duplicate audio playback (if hooks trigger sounds)
  • Duplicate file writes/modifications

What Should Happen?

Each hook defined in a plugin should be:

  1. Registered once in the hooks system
  2. Displayed once in /hooks command output
  3. Executed once when triggered

Error Messages/Logs

**Log file evidence (`~/claude-hook-duplicate-test.log`):**


[2025-11-03 05:19:02.918] Hook: SessionStart | PID: 63864
[2025-11-03 05:19:02.919] Hook: SessionStart | PID: 63865
[2025-11-03 05:19:37.800] Hook: Notification | PID: 64293
[2025-11-03 05:19:37.800] Hook: Notification | PID: 64292


**Observations:**
- SessionStart executed twice (PID 63864 and 63865) within 1ms
- Notification executed twice (PID 64292 and 64293) at identical timestamp
- Each pair has **different PIDs**, proving these are separate process executions

**`/hooks` command output:**

Shows "2 hooks" for PreCompact when only 1 hook is defined in the plugin (see attached screenshot).

Steps to Reproduce

Reproduction repository: https://github.com/otolab/hook-duplicate-test-plugin

Quick reproduction steps:

  1. Install the test plugin:

``bash
/plugin marketplace add otolab/hook-duplicate-test-plugin
/plugin install hook-duplicate-test@otolab-marketplace
``

  1. Check hook registration:

``bash
/hooks
``
Expected: Each hook type shows "1 hook"
Actual: Shows "2 hooks" (duplicate registration)

  1. Clear log file and trigger hooks:

``bash
rm -f ~/claude-hook-duplicate-test.log
# Start new session (triggers SessionStart)
# Or trigger notification (triggers Notification)
``

  1. Check execution log:

``bash
cat ~/claude-hook-duplicate-test.log
``
Expected: One entry per hook execution
Actual: Two entries with different PIDs

Screenshot of /hooks command showing duplicate registration:

!PreCompact showing 2 hooks when only 1 is defined

As shown in the screenshot, the PreCompact section displays "[Plugin] 2 hooks" even though only one PreCompact hook is defined in the plugin configuration.

Claude Model

Sonnet (default)

Is this a regression?

I don't know

Last Working Version

Unknown

Claude Code Version

2.0.31 (Claude Code)

Platform

Google Vertex AI

Operating System

macOS

Terminal/Shell

iTerm2

Additional Information

Affected hook types (confirmed):

  • SessionStart
  • Notification
  • PreCompact
  • Likely affects all hook types

Related but different issues:

  • Issue #10777: Skill execution message appears twice (display-only, not actual duplicate execution)
  • Issue #6674: Hook navigation infinite loop (UI navigation issue with duplicate entries)

This issue is distinct because it involves actual duplicate execution with separate processes, not just display problems.

Impact:

  • Medium severity
  • Causes duplicate side effects (notifications, file writes, audio, etc.)
  • Appears to have been present since plugin system launch

Test plugin details:

  • Simple hooks that log timestamp + PID to file
  • Demonstrates the issue is in the hook registration/execution system, not in hook implementation
  • Repository includes full reproduction instructions

View original on GitHub ↗

13 Comments

github-actions[bot] · 10 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/3465
  2. https://github.com/anthropics/claude-code/issues/3523
  3. https://github.com/anthropics/claude-code/issues/9602

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

otolab · 10 months ago

This issue is not a duplicate of the suggested issues:

  • #3465: Only occurred when running from home directory. This issue occurs regardless of working directory and is specific to plugin-registered hooks.
  • #3523: Hooks progressively duplicated (2x→5x→10x+) during sessions. This issue shows consistent 2x execution from the start, not progressive.
  • #9602: UI rendering bug only - hooks executed once but UI showed duplicates. This issue involves actual duplicate execution with different PIDs (proven via log files showing two separate processes).

Key distinction: This is a plugin hook registration issue causing real duplicate execution, verified by different process IDs in logs.

otolab · 10 months ago

🤖 by Claude Code

I've resolved this issue myself. Apologies for the confusion!

Root Cause

The duplicate hook execution was caused by the same file being loaded twice in the plugin system's hook loading process.

How It Happens

  1. Automatic loading of default path
  • hooks/hooks.json is automatically loaded if it exists
  1. Loading from plugin.json's hooks field
  • When plugin.json contains "hooks": "./hooks/hooks.json", the same file is loaded again
  1. Hook merge process
  • The merge function (yGQ) simply concatenates arrays without checking for duplicates
  • This results in the same hooks being registered twice

Problem in the Test Plugin

The issue was in my test plugin's plugin.json:

{
  "name": "hook-duplicate-test",
  "hooks": "./hooks/hooks.json"  // ← This causes the duplication
}

Removing this field (or specifying a different path than the default) prevents the duplication.

Code Investigation

I couldn't find the unminified source code, but Claude Code (Sonnet 4.5) analyzed the minified cli.js and identified the problem areas.

Plugin loading process (relevant part of _GQ function):

// 1. Automatic loading of default path
let V, K = XI(A, "hooks", "hooks.json");  // path to hooks/hooks.json
if (G.existsSync(K))
  try {
    V = SGQ(K, J.name)  // ← First load
  } catch (D) { ... }

// 2. Processing plugin.json's hooks field
if (J.hooks) {  // J.hooks is the hooks field from plugin.json
  let D = Array.isArray(J.hooks) ? J.hooks : [J.hooks];
  for (let E of D)
    if (typeof E === "string") {  // E = "./hooks/hooks.json"
      let H = XI(A, E);
      if (!G.existsSync(H)) { ... continue }
      try {
        let w = SGQ(H, J.name);  // ← Second load (same file!)
        V = yGQ(V, w)  // ← Merge causes duplication
      } catch (w) { ... }
    }
}

Hook merge process (yGQ function):

function yGQ(A, B) {
  if (!A) return B;
  let Q = {...A};
  for (let [I, G] of Object.entries(B))
    if (!Q[I])
      Q[I] = G;
    else 
      Q[I] = [...Q[I] || [], ...G];  // ← Concatenates arrays (no duplicate check)
  return Q
}

Suggestion

Since this issue is subtle and easy to miss, I think it would be helpful to prevent duplicate file loading by comparing absolute paths. Tracking already-loaded file paths would prevent the same file from being loaded twice.

This issue can now be closed.

dicksontsai collaborator · 9 months ago

Thanks for your report!

In https://github.com/otolab/hook-duplicate-test-plugin/blob/main/plugins/hook-duplicate-test/.claude-plugin/plugin.json , you shouldn't specify "hooks": "./hooks/hooks.json" because we already load the hooks folder automatically. This "hooks" field in the JSON causes the duplicate registration -- it's meant for registering extra hooks not already automatically imported.

However, this is quite the footgun, so I'm leaving this open for the team to figure out the best handle this situation.

otolab · 9 months ago

Thanks for confirming this is a known issue. I'll look forward to the team's decision on the best approach to handle this.

github-actions[bot] · 8 months ago

This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.

kylesnowschwartz · 8 months ago

Still hitting this on 2.0.x (latest). Seeing 4x output for bumper-lanes plugin hooks:

PostToolUse:Bash hook returned blocking error: ✓ Bumper lanes: Auto-reset after commit.
PostToolUse:Callback hook returned blocking error: ✓ Bumper lanes: Auto-reset after commit.
PostToolUse:Bash hook returned blocking error: ✓ Bumper lanes: Auto-reset after commit.
PostToolUse:Callback hook returned blocking error: ✓ Bumper lanes: Auto-reset after commit.

The 4x is: 2 tool matchers × 2 duplicate executions (the bug).

This is particularly noisy for hooks that provide user feedback since every message gets doubled. Would love to see this prioritized - it's been open since early November.

kylesnowschwartz · 8 months ago

See also #14281 which reports the same symptom from the hook additionalContext angle.

kylesnowschwartz · 8 months ago

@dicksontai The bumper-lanes plugin (source) does not have a "hooks" field in plugin.json - it only uses auto-discovery from the hooks/ folder:

{
  "name": "claude-bumper-lanes",
  "version": "3.0.0",
  "description": "Enforces git diff thresholds...",
  ...
}

Yet we still see duplicate execution:

PostToolUse:Bash hook returned blocking error: ✓ Bumper lanes: Auto-reset
PostToolUse:Callback hook returned blocking error: ✓ Bumper lanes: Auto-reset
PostToolUse:Bash hook returned blocking error: ✓ Bumper lanes: Auto-reset
PostToolUse:Callback hook returned blocking error: ✓ Bumper lanes: Auto-reset

So the duplicate execution bug exists independently of the "hooks" footgun.

kylesnowschwartz · 8 months ago

<img width="652" height="244" alt="Image" src="https://github.com/user-attachments/assets/86147313-5321-4bf6-87b8-59a6581ab26a" />

<img width="655" height="113" alt="Image" src="https://github.com/user-attachments/assets/14115bad-f619-4a25-8074-38f98d3f80a1" />

@dhollman / @dicksontsai
Can you review my plugin configurations and double check it's not user error?
https://github.com/kylesnowschwartz/SimpleClaude/tree/main/plugins/sc-hooks
https://github.com/kylesnowschwartz/claude-bumper-lanes/tree/main/bumper-lanes-plugin

Is it possible that plugin hooks from the plugins/cache dir and plugins/marketplaces dir are causing duplicate hook registrations? Notice even the Anthropic 'ralph-wiggum' hook is listed twice.

github-actions[bot] · 7 months ago

This issue has been automatically closed due to 60 days of inactivity. If you're still experiencing this issue, please open a new issue with updated information.

marcindulak · 7 months ago

This issue was closed incorrectly despite recent human comments. This behavior of the bot is reported at https://github.com/anthropics/claude-code/issues/16497. Please upvote that issue, so maybe it gets noticed.

github-actions[bot] · 6 months ago

This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.