# Common Performance Bottlenecks in Claude Plugin Architecture

> Discover common performance bottlenecks in Claude plugin architecture, including synchronous hook execution, regex compilation, and file I/O that block the UI. Optimize your plugins now.

- Repository: [Anthropic/claude-plugins-official](https://github.com/anthropics/claude-plugins-official)
- Tags: performance
- Published: 2026-03-13

---

**Claude plugin hooks execute synchronously before every tool call, so latency in regex compilation, file I/O, and rule evaluation directly blocks the user interface.**

Every tool invocation in Claude Code triggers a sandboxed hook script that must complete before the operation proceeds. In the `anthropics/claude-plugins-official` repository, these hooks—[`pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/pretooluse.py), [`posttooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/posttooluse.py), and [`stop.py`](https://github.com/anthropics/claude-plugins-official/blob/main/stop.py)—follow a rigid three-step workflow: read JSON from `stdin`, load rule files via `load_rules()`, and evaluate conditions through `RuleEngine.evaluate_rules()`. Because this process runs inside a fresh interpreter for each call, inefficiencies in the core libraries compound into user-visible delays.

## The Synchronous Hook Lifecycle

Understanding the execution path is critical to identifying bottlenecks. When Claude Code invokes a tool, the sandbox spawns a new Python process that executes the relevant hook script in `plugins/hookify/hooks/`. The script orchestrates three sequential operations:

1. **Input deserialization**: `json.load(sys.stdin)` parses the tool request (line 30 of [`pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/pretooluse.py)).
2. **Rule hydration**: `load_rules()` in [`plugins/hookify/core/config_loader.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/core/config_loader.py) discovers and parses every `.claude/hookify.*.local.md` file using `glob.glob()` (lines 13‑28).
3. **Condition evaluation**: `RuleEngine.evaluate_rules()` in [`plugins/hookify/core/rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/core/rule_engine.py) tests each rule against the input, invoking `_regex_match()` and `_extract_field()` for every condition.

The process terminates with `sys.exit(0)` (line 62 of [`pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/pretooluse.py)), forcing the sandbox to spawn a fresh interpreter for the next tool call. Any latency in steps 1‑3 blocks the user interface.

## Six Critical Performance Bottlenecks

The following inefficiencies dominate execution time in high-frequency workflows.

### Repeated Regex Compilation

The `_regex_match()` method historically compiled regular expressions on every evaluation before an optimization introduced an LRU cache. Even with `@lru_cache(maxsize=128)` in `compile_regex()` (lines 13‑24 of [`rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/rule_engine.py)), cache misses for large rule sets trigger expensive compilation. Each microsecond of compilation accumulates across dozens of rules per hook call.

### Redundant File System Scanning

`load_rules()` executes `glob.glob('.claude/hookify.*.local.md')` on every hook invocation (lines 13‑28 of [`config_loader.py`](https://github.com/anthropics/claude-plugins-official/blob/main/config_loader.py)). On networked file systems or high-latency storage, this directory traversal and subsequent YAML parsing creates multi-millisecond stalls before any rule logic executes.

### Per-Hook JSON Parsing

While `json.load(sys.stdin)` in [`pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/pretooluse.py) (lines 30‑33) is relatively lightweight, the repeated allocation and parsing of identical schema structures adds unnecessary overhead when combined with file I/O and regex work.

### Expensive Field Extraction

The `_extract_field()` method in [`rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/rule_engine.py) (lines 202‑226) traverses a long `if/elif` chain and may read entire transcript files via `with open(transcript_path, 'r')` for Stop events. Loading large text files into memory for simple metadata checks consumes significant RAM and CPU cycles.

### Linear Rule Set Scaling

The `load_rules()` function iterates over every discovered rule file in a linear `for file_path in files:` loop. Users with dozens or hundreds of [`.local.md`](https://github.com/anthropics/claude-plugins-official/blob/main/.local.md) files incur O(N) loading costs per hook, with no early-exit optimization for event-specific subsets.

### Process Startup Penalty

Each hook script terminates with `sys.exit(0)`, destroying the Python interpreter. The sandbox must then spawn a new process, re-import modules, and re-initialize the environment for the subsequent tool call, adding tens to hundreds of milliseconds of fixed overhead.

## Optimization Strategies

Mitigating these bottlenecks requires caching, pre-compilation, and architectural shifts toward long-lived processes.

### Cache Rule Files in Memory

Persist loaded rules across hook calls using a module-level dictionary. This eliminates disk I/O after the first invocation:

```python

# plugins/hookify/core/config_loader.py

from typing import List, Optional
from dataclasses import dataclass

_RULE_CACHE: dict[str, List[Rule]] = {}

def load_rules(event: Optional[str] = None) -> List[Rule]:
    cache_key = event or "all"
    if cache_key in _RULE_CACHE:
        return _RULE_CACHE[cache_key]  # Return cached rules

    
    rules: List[Rule] = []
    # Existing glob and YAML parsing logic...

    # (lines 13-28 original implementation)

    
    _RULE_CACHE[cache_key] = rules     # Store for subsequent calls

    return rules

```

### Pre-Compile Regex Patterns at Load Time

Move regex compilation from evaluation time to rule loading time. Store compiled patterns on the condition objects:

```python

# plugins/hookify/core/config_loader.py

def _precompile_conditions(rules: List[Rule]) -> None:
    for rule in rules:
        for cond in rule.conditions:
            cond._compiled = compile_regex(cond.pattern)

# Call immediately before caching

_precompile_conditions(rules)

```

Then modify the matching logic to use the stored pattern:

```python

# plugins/hookify/core/rule_engine.py

def _regex_match(self, condition: Condition, text: str) -> bool:
    return bool(condition._compiled.search(text))

```

### Minimize Field Extraction Scope

Refactor `_extract_field()` to accept a whitelist of required fields based on the active rule set. Skip the `if/elif` chain for tool types not referenced by current rules, and replace full transcript file reads with pre-computed metadata hashes.

### Implement Rule Batching

Group rules by `tool_matcher` or `event` type during loading. Evaluate only the subset relevant to the current hook context rather than testing every rule against every input.

### Adopt the Agent SDK for Persistence

The `plugins/agent-sdk-dev/` directory provides a framework for long-running plugin processes. Instead of spawning a fresh interpreter per tool call, implement a persistent agent that imports `RuleEngine` once and processes hook requests via IPC:

```bash

# Start the persistent agent once

python -m plugins.agent_sdk_dev.agents.agent_sdk_verifier_py

```

This eliminates interpreter startup costs and allows the rule cache to persist across the entire Claude Code session.

## Key Source Files for Performance Tuning

| File | Critical Function | Performance Impact |
|------|-------------------|-------------------|
| [`plugins/hookify/core/rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/core/rule_engine.py) | `evaluate_rules()`, `_regex_match()`, `_extract_field()` | Contains the regex LRU cache and field extraction logic; optimize here to reduce per-rule CPU time. |
| [`plugins/hookify/core/config_loader.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/core/config_loader.py) | `load_rules()` | Controls file discovery and YAML parsing; primary source of I/O latency. |
| [`plugins/hookify/hooks/pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/hooks/pretooluse.py) | Main execution flow (lines 30‑62) | Orchestrates the bottleneck pipeline; termination via `sys.exit(0)` forces process respawn. |
| [`plugins/hookify/hooks/stop.py`](https://github.com/anthropics/claude-plugins-official/blob/main/plugins/hookify/hooks/stop.py) | Transcript file handling | Demonstrates expensive file reads in `_extract_field()` for Stop events. |
| `plugins/agent-sdk-dev/` | Persistent process framework | Offers the architectural path to eliminate startup overhead. |

## Summary

- **Regex compilation** is mitigated by pre-compiling patterns during rule loading and utilizing the LRU cache in [`rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/rule_engine.py).
- **File I/O latency** dominates hook execution; cache rule files in memory using a global dictionary in [`config_loader.py`](https://github.com/anthropics/claude-plugins-official/blob/main/config_loader.py).
- **Process spawning** adds fixed overhead per tool call; migrate to the Agent SDK in `plugins/agent-sdk-dev/` for persistent execution.
- **Field extraction** costs escalate with transcript file sizes; limit extraction to referenced fields only.
- **Rule set scaling** is linear; batch rules by event type and impose limits on active rule counts.

## Frequently Asked Questions

### Why does every Claude tool call feel slower when plugins are enabled?

Each tool invocation triggers a fresh Python process that must rediscover and reload all rule files from disk before executing. The synchronous `load_rules()` and `evaluate_rules()` calls in [`pretooluse.py`](https://github.com/anthropics/claude-plugins-official/blob/main/pretooluse.py) block the Claude Code interface until completion, making file system latency and regex compilation directly perceptible as UI delays.

### How much performance improvement can I expect from caching rule files?

Caching eliminates the `glob.glob()` and YAML parsing overhead in [`config_loader.py`](https://github.com/anthropics/claude-plugins-official/blob/main/config_loader.py) (lines 13‑28). On networked file systems, this typically reduces hook execution from 50‑200 ms to under 5 ms for subsequent calls within the same session. The first call remains uncached, but all subsequent tool invocations benefit.

### Is the LRU cache for regexes sufficient for large rule sets?

The default `@lru_cache(maxsize=128)` in [`rule_engine.py`](https://github.com/anthropics/claude-plugins-official/blob/main/rule_engine.py) handles moderate rule sets effectively. However, if users maintain more than 128 unique regex patterns across all [`.local.md`](https://github.com/anthropics/claude-plugins-official/blob/main/.local.md) files, cache eviction will trigger re-compilation. Pre-compiling patterns during rule loading (storing them as `cond._compiled`) bypasses this limit entirely.

### Can I use the Agent SDK with existing hookify plugins?

Yes. The `plugins/agent-sdk-dev/` framework is designed to replace the spawn-and-exit model of traditional hooks. Instead of writing scripts that terminate with `sys.exit(0)`, you implement a long-lived agent that imports the `RuleEngine` once and processes requests via the SDK's IPC mechanism, maintaining your rule cache and compiled regexes across hundreds of tool calls.