Hook Runtime: Add centralized hook manager with parallel dispatch and priority ordering
Problem
As the Claude Code plugin ecosystem grows, hooks are becoming a performance and security bottleneck. Each plugin registers its own hooks independently, and Claude Code runs them all sequentially — every registered hook must complete (or timeout) before the next starts and before the actual tool call proceeds.
Real-world impact (measured on a moderately-equipped setup)
With plugins like read-back, hookify, security-guidance, cc10x, and claude-dev-infrastructure installed:
| Tool call type | PreToolUse hooks | PostToolUse hooks | Total hooks per call |
|---|---|---|---|
| Read/Grep/Glob | 2 (unfiltered) | 2 (unfiltered) | 4 |
| Edit/Write | 5 (all match) | 3 (all match) | 8 |
At ~10-50ms each, that's 80-400ms of hook overhead per Edit/Write — and this scales linearly with every new plugin. Adding a monitoring tool like cctop would push Edit/Write to 10 hooks per call.
The deeper issue
There are two fundamentally different categories of hooks being treated identically:
- Side-channel hooks — write logs, update state, track metrics. Their stdout is empty. They don't affect Claude's behavior. (e.g., cctop, cc10x audit logging, read-back state tracking)
- Flow-control hooks — return
permissionDecision: deny,systemMessage,decision: block. These are part of the LLM conversation loop. (e.g., security-guidance blocking dangerous patterns, hookify rule engine, ralph-loop continuing iterations)
Currently both categories run sequentially and block the pipeline identically. A logging hook that writes to a file has the same execution cost as a security hook that needs to make a block/allow decision.
Proposal: Smarter Hook Runtime
1. Hook classification via manifest flag
Allow plugins to declare their hooks' nature:
{
"hooks": {
"PreToolUse": [{
"sideEffectOnly": true,
"hooks": [{ "type": "command", "command": "..." }]
}]
}
}
"sideEffectOnly": true— hook cannot block, stdout is ignored. Claude Code can fire-and-forget without waiting.- Default (
false) — current behavior, sequential execution with result handling.
2. Parallel dispatch for side-effect hooks
Side-effect hooks should run in parallel and non-blocking. The tool call proceeds immediately while they execute in the background. This alone would cut the overhead roughly in half for typical setups.
3. Priority ordering for flow-control hooks
Allow plugins to declare execution priority:
{
"hooks": {
"PreToolUse": [{
"priority": 10,
"hooks": [{ "type": "command", "command": "..." }]
}]
}
}
Lower number = runs first. Security hooks should run before convenience hooks. This also enables short-circuiting — if a high-priority hook blocks, lower-priority hooks can be skipped.
4. Output validation / sandboxing
Currently any plugin can return permissionDecision: deny and block any tool call with no oversight. Consider:
- A
"canBlock": truepermission that plugins must explicitly declare (and users approve) - Size caps on
systemMessageinjection to prevent context window abuse - Telemetry/logging of which hooks block and how often, so users can audit
5. (Stretch) Built-in hook manager / event bus
Long-term, a centralized hook manager within Claude Code that plugins register with, rather than each spawning their own process:
Current: Claude Code → hook1 (wait) → hook2 (wait) → hook3 (wait) → proceed
Proposed: Claude Code → Runtime Manager
├── parallel: side-effect hooks (fire & forget)
├── sequential: flow-control hooks (by priority)
└── single aggregated response → proceed
Benefits
- Performance: Parallel dispatch for observe-only hooks could cut hook overhead by 50%+
- Security: Explicit
canBlockpermissions prevent rogue plugins from hijacking tool calls - Scalability: The plugin ecosystem can grow without linearly degrading performance
- Transparency: Users can see which hooks block, how often, and at what cost
Context
This was identified by auditing the data flow of 10 active plugin hooks across 7 event types. Happy to share the full hook data flow analysis if useful.
🤖 Generated with Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗