Plugin hooks.json registrations multiply additively (1×→122×) in multi-agent sessions (v2.1.156)
Summary
In multi-agent sessions, a plugin's hooks/hooks.json registrations multiply additively — each command registration object is duplicated N times, so every hook fires N times per tool call. Observed growth: 1× → 122× for a directory-source plugin. At high multiples the duplicated hook execution pollutes tool output with garbled content.
Environment
- Claude Code 2.1.156
- macOS (darwin 25.4.0)
- Plugin installed from a local/directory-source marketplace (
"type": "local","source": "./"). The plugin is registered both as a directory source and as a cached install; both start at the canonical registration count.
What happens
hooks/hooks.jsonstarts at its canonical count (one registration object per hook).- During a session that dispatches sub-agents (the
Agenttool / Subagent lifecycle), the working-treehooks.jsongrows — registration objects are duplicated additively (observed up to 122× per hook). git checkout -- hooks/hooks.jsonrestores it, but it can re-grow.- At high multiples, hooks fire N times each and tool output is corrupted.
The growth is purely additive (duplicated registration objects), consistent with the plugin-hook load/merge path re-running and appending the plugin's hook set rather than replacing it.
Why this looks like the harness, not the plugin
A full audit of the plugin's own scripts found no additive writer to hooks.json — the only writer overwrites the file with the canonical copy (atomic tmp+mv). The only mechanism that fits a 1×→N× additive growth is the harness re-running its plugin-hook load/merge and appending.
Reproduction status
Intermittent. It did not reproduce in a focused session (held at the canonical count across a no-op sub-agent dispatch + two parallel real-work agents + ~14 tool calls). No deterministic minimal repro yet — filing with the negative-reproduction record rather than a guaranteed repro, in case others can correlate.
Correct metric
Count structurally, not by line-grep:
jq '[.. | objects | select(.type=="command")] | length' hooks/hooks.json
(grep -c command double-counts — the "type" line plus the "command" line.)
Ask
- Is the plugin-hook load/merge expected to be idempotent across sub-agent dispatch / plugin reload? A duplicated-registration guard (or replace-not-append semantics) on the merge path would prevent this.
- Any known trigger for plugin reload mid-session that would re-run the merge?
Happy to add more detail or test a patch.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗