# How to Debug Hook and Plugin Issues in Claude Code: Complete Troubleshooting Guide

> Debug hook and plugin issues in Claude Code by verifying rule loading, evaluating matches with RuleEngine, and testing hooks using JSON payloads. Enhance your Claude Code development.

- Repository: [Anthropic/claude-code](https://github.com/anthropics/claude-code)
- Tags: how-to-guide
- Published: 2026-04-02

---

**Debug hook and plugin issues in Claude Code by verifying rule file loading in [`config_loader.py`](https://github.com/anthropics/claude-code/blob/main/config_loader.py), evaluating rule matches through `RuleEngine.evaluate_rules()`, and testing hooks manually via JSON payloads piped to the script with `ENABLE_SECURITY_REMINDER=1` enabled.**

Claude Code's extensibility relies on a sophisticated hook and plugin system managed by the **hookify** framework. When you need to debug hook and plugin issues in Claude Code, understanding the execution flow from JSON payload generation to rule evaluation in [`plugins/hookify/core/rule_engine.py`](https://github.com/anthropics/claude-code/blob/main/plugins/hookify/core/rule_engine.py) is essential. This guide walks through the exact diagnostic steps used in the `anthropics/claude-code` repository to identify why hooks block tools silently, fail to load rules, or crash during execution.

## Understanding the Hook and Plugin Architecture

Claude Code processes **hooks** and **plugins** as separate Python processes that intercept tool invocations through a standardized JSON interface. The execution follows a strict pipeline from payload generation to rule-based decision making.

### Hook Discovery and Rule Loading

The system scans for rule files matching `.claude/hookify.*.local.md` via the `load_rules()` function in [`plugins/hookify/core/config_loader.py`](https://github.com/anthropics/claude-code/blob/main/plugins/hookify/core/config_loader.py). Each discovered file is parsed into `Rule` objects using `Rule.from_dict`, which compiles conditions into `Condition` objects capturing the field, operator, and pattern. If `enabled: false` appears in the YAML frontmatter, the loader skips that file entirely.

### Rule Evaluation Logic

The `RuleEngine` class in [`plugins/hookify/core/rule_engine.py`](https://github.com/anthropics/claude-code/blob/main/plugins/hookify/core/rule_engine.py) evaluates every loaded rule against incoming payloads through `evaluate_rules()`. When a payload matches a rule with `action: "block"`, the engine returns a *deny* decision for `PreToolUse`/`PostToolUse` hooks or a *block* response for `Stop` events. Non-blocking matches return only a `systemMessage` warning or an empty dictionary for allow decisions.

### Security Hook Reference Implementation

The built-in security reminder hook at [`plugins/security-guidance/hooks/security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/plugins/security-guidance/hooks/security_reminder_hook.py) demonstrates production patterns for file-editing operations. It reads JSON from `stdin`, extracts file paths and content via `extract_content_from_input()`, then calls `check_patterns()` against path-based or substring-based rules. The script maintains per-session state in `~/.claude/security_warnings_state_<session_id>.json` through `load_state()` and `save_state()` to prevent duplicate warnings, emitting diagnostics to [`/tmp/security-warnings-log.txt`](https://github.com/anthropics/claude-code/blob/main//tmp/security-warnings-log.txt) via the `debug_log` helper.

## Common Symptoms and Diagnostic Targets

When hooks malfunction, symptoms map directly to specific failure points in the architecture.

| Symptom | Likely Cause | Where to Look |
|---------|--------------|---------------|
| **No warning appears** while a rule should match | Rule file not loaded, wrong event filter, or `enabled: false` | `load_rules()` → `load_rule_file()` in [`config_loader.py`](https://github.com/anthropics/claude-code/blob/main/config_loader.py) |
| **Hook blocks unexpectedly** | A rule's `action` is `"block"` or the hook returns exit code `2` | `RuleEngine.evaluate_rules()` decision logic |
| **Hook crashes silently** | Uncaught exception in `main()`; JSON parsing failure | Check `debug_log` output or `stderr` in [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) |
| **State file not updating** | Permission issues or JSON corruption | `load_state()` / `save_state()` in [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) |
| **Performance lag** | Large number of rule files or heavy regexes | Regex compilation (`compile_regex` uses `lru_cache`) in [`rule_engine.py`](https://github.com/anthropics/claude-code/blob/main/rule_engine.py) |

## Step-by-Step Debugging Techniques

### Enable Verbose Logging

Set the environment variable `ENABLE_SECURITY_REMINDER=1` (enabled by default) and monitor the debug log:

```bash
tail -f /tmp/security-warnings-log.txt

```

This captures JSON parsing errors, rule match details, and state file operations from [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py).

### Test Hooks Manually with JSON Payloads

Simulate a `PreToolUse` hook invocation by piping a JSON payload directly to the hook script:

```bash
payload='{
  "session_id": "test123",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "scripts/deploy.yml",
    "content": "run: echo \"${{ github.event.issue.title }}\""
  }
}'
echo "$payload" | python3 plugins/security-guidance/hooks/security_reminder_hook.py

```

If the payload matches a blocking rule, the script prints the warning to `stderr` and exits with status `2`. Non-blocking matches exit with status `0` after printing warnings.

### Inspect Active Rule Sets Programmatically

Add temporary debugging output to [`plugins/hookify/core/config_loader.py`](https://github.com/anthropics/claude-code/blob/main/plugins/hookify/core/config_loader.py) to verify which rules are loaded:

```python
import json
from hookify.core.config_loader import load_rules

# Load only 'bash' event rules for faster iteration

rules = load_rules(event='bash')
print(json.dumps([r.__dict__ for r in rules], indent=2))

```

Run any Claude Code command to execute this code and dump the active rule set, confirming that your `.claude/hookify.*.local.md` files are actually being parsed.

### Reset Per-Session State

Delete state files to reset warning counters when testing repeated operations:

```bash
session="my-session"
state_file=$(python3 -c "import os; print(os.path.expanduser(f'~/.claude/security_warnings_state_{session}.json'))")
rm -f "$state_file"
echo "State reset for session $session"

```

This forces [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) to treat the next matching operation as the first occurrence.

### Validate Custom Rule Files

Create [`.claude/hookify.insecure.local.md`](https://github.com/anthropics/claude-code/blob/main/.claude/hookify.insecure.local.md) to test the blocking mechanism:

```yaml
---
name: dangerous-curl
enabled: true
event: bash
action: block
tool_matcher: Bash
conditions:
  - field: command
    operator: contains
    pattern: "curl http://"
---
⚠️ Using `curl` over an insecure HTTP endpoint can expose credentials. Use HTTPS or sanitize the URL.

```

When you run a Bash tool containing `curl http://`, the hook blocks the command and displays the warning, validating that your rule file is correctly loaded and evaluated.

## Summary

- **Hook execution** relies on JSON payloads passed via `stdin` to Python scripts in `plugins/`, with exit code `2` triggering blocks in `PreToolUse` hooks.
- **Rule debugging** requires verifying `.claude/hookify.*.local.md` files are loaded by `load_rules()` in [`config_loader.py`](https://github.com/anthropics/claude-code/blob/main/config_loader.py) and parsed into valid `Rule` objects.
- **State management** happens in `~/.claude/security_warnings_state_<session_id>.json`; delete these files to reset per-session warning counters.
- **Manual testing** via piped JSON payloads to [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) provides immediate feedback without running full Claude Code sessions.
- **Performance issues** stem from unoptimized regex patterns in [`rule_engine.py`](https://github.com/anthropics/claude-code/blob/main/rule_engine.py) or excessive rule file counts affecting `compile_regex` with `lru_cache`.

## Frequently Asked Questions

### Why does my hook silently fail to block dangerous commands?

Silent failures typically indicate JSON parsing exceptions in [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) or the hook exiting with status `0` instead of `2`. Check [`/tmp/security-warnings-log.txt`](https://github.com/anthropics/claude-code/blob/main//tmp/security-warnings-log.txt) for `debug_log` output, and verify your rule uses `action: block` rather than just emitting a warning message. Ensure the `event:` filter in your YAML matches the hook type (`PreToolUse`, `PostToolUse`, or `bash`).

### How do I check which rules are currently loaded?

Import `load_rules` from `hookify.core.config_loader` and print the rule dictionary representations. Run this in a Python script or add it temporarily to [`config_loader.py`](https://github.com/anthropics/claude-code/blob/main/config_loader.py): `print(json.dumps([r.__dict__ for r in load_rules()], indent=2))`. This reveals whether your `.claude/hookify.*.local.md` files are discovered and whether `enabled: false` is preventing loading.

### What causes the "security_warnings_state" file errors?

State file errors occur when `load_state()` or `save_state()` in [`security_reminder_hook.py`](https://github.com/anthropics/claude-code/blob/main/security_reminder_hook.py) encounter permission denied errors on `~/.claude/` or JSON decoding failures from corrupted state files. Ensure the directory exists with write permissions, or delete the specific `security_warnings_state_<session_id>.json` file to force regeneration.

### Can I disable hooks temporarily for testing?

Set `ENABLE_SECURITY_REMINDER=0` (or remove the environment variable) to disable the security reminder hook entirely. For other hooks, rename or move the corresponding `.claude/hookify.*.local.md` rule files, or set `enabled: false` in the YAML frontmatter to prevent `load_rules()` from including them in the `RuleEngine` evaluation.