Skip to content
Back to skills

Plugin Hooks

ASecurity

Authoritative guide to Claude Code hooks — event-driven scripts that execute before or after tool calls, session events, file changes, and more. Use when writing a PreToolUse hook to block dangerous commands, a PostToolUse hook to auto-format after edits, a SessionStart hook to inject context, a Stop hook for session loops, a Notification hook for desktop alerts, a FileChanged hook for reactive environments, a WorktreeCreate hook for custom worktree provisioning, or current documented hook ev...

  • 11 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 28, 2026
ai-agentsjavascripttypescriptpythongojavashellbashreacttestingdebugging

Works with

  • claude code
  • mcp

Security analysis

A100/100

Pro scans all 7 files and shows the line behind each finding

Scanned May 28, 2026

npx -y skills add viktorbezdek/skillstack --skill plugin-hooks --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Plugin Hooks?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Plugin Hooks
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/viktorbezdek-plugin-hooks/badge)](https://www.skillsdirectory.com/skills/viktorbezdek-plugin-hooks)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: plugin-hooks
description: Authoritative guide to Claude Code hooks — event-driven scripts that execute before or after tool calls, session events, file changes, and more. Use when writing a PreToolUse hook to block dangerous commands, a PostToolUse hook to auto-format after edits, a SessionStart hook to inject context, a Stop hook for session loops, a Notification hook for desktop alerts, a FileChanged hook for reactive environments, a WorktreeCreate hook for custom worktree provisioning, or current documented hook events. Covers handler types (command, http, mcp_tool, prompt, agent), matcher syntax (exact/OR-list/regex), exit code semantics, and JSON output schema. NOT for designing hook script content for a specific domain (use the domain skill) — this skill covers hook mechanics and authoring only.
---

# Plugin Hooks

> The most powerful and most trap-laden extension type. Exit code 1 does not block — only exit code 2 blocks. Matcher syntax has three modes. `updatedInput` replaces the entire tool input object, not just one field. These three facts cause most hook bugs. Learn them first.

Authoritative source: [https://code.claude.com/docs/en/hooks](https://code.claude.com/docs/en/hooks)

---

## When to use this skill

- Writing any hook configuration (hooks/hooks.json, settings.json)
- Debugging "my hook isn't blocking" or "my hook isn't firing"
- Choosing between hook event types for a specific capability
- Understanding exit code behavior (exit 1 vs exit 2 distinction)
- Designing safe auto-approve flows for PermissionRequest
- Setting up file-watching or cwd-change hooks

## When NOT to use this skill

- **What hook script logic to write** (domain-specific) → use the domain skill
- **MCP server authoring** → use `plugin-architecture` or `mcp-server`
- **Full plugin structure** → use `plugin-composition`

---

## Core principles

1. **Exit code 1 does not block.** Only exit code 2 blocks a tool call, prompt, or stop. Exit 1 is a non-blocking error — the action proceeds. This is the #1 source of hook bugs.
2. **Matcher syntax has three modes.** `*` or omitted = match all. Letters/digits/underscore/pipe only = exact string or `|`-separated list. Any other character = JavaScript regex. `mcp__memory` is an exact match (matches no real tool); use `mcp__memory__.*` for regex.
3. **`updatedInput` replaces the entire tool input.** In PreToolUse, returning `updatedInput` in `hookSpecificOutput` replaces ALL fields. You must echo back unchanged fields or they are lost.
4. **Async hooks cannot block.** If `async: true`, the hook runs after the action has already proceeded. Never use async for security/guardrail hooks.

---

## Core events (most common in practice)

The full documented event set changes over time. `references/hook-event-reference.md` tracks the current catalog; this section covers the events practitioners reach for most often.

### PreToolUse
Fires before any tool call. Can block, modify input, or allow. The most powerful event.

```json
{
  "matcher": "Bash",
  "hooks": [{"type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/check.sh"}]
}
```

Input includes: `tool_name`, `tool_input` (full schema), `tool_use_id`.

Decision output:
```json
{"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Blocked"}}
```
Precedence when multiple hooks run: deny > defer > ask > allow.

### PostToolUse
Fires after a tool succeeds. Cannot block. Common use: auto-format, logging, notifications.

```json
{"matcher": "Edit|Write", "hooks": [{"type": "command", "async": true, "command": "prettier --write \"$file_path\""}]}
```

### PermissionRequest
Fires when a permission dialog would appear. Can auto-approve or deny without showing the dialog.

```json
{"hookSpecificOutput": {"hookEventName": "PermissionRequest", "decision": {"behavior": "allow"}}}
```

### UserPromptSubmit
Fires before Claude processes a user prompt. Can block or add context. No matcher support — fires on every prompt.

### SessionStart
Fires at session start, resume, clear, or compact. Can inject context Claude will see.

```json
{"matcher": "compact", "hooks": [{"type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/inject-context.sh"}]}
```

Exit 0 stdout is added to Claude's context for `SessionStart`.

### Stop
Fires when Claude finishes responding. Can prevent Claude from stopping to continue working.

⚠️ **Infinite loop trap**: if your Stop hook always signals "keep going", Claude loops forever. Check `stop_hook_active` in input:
```bash
input=$(cat)
if echo "$input" | python3 -c "import json,sys; d=json.load(sys.stdin); sys.exit(0 if d.get('stop_hook_active') else 1)" 2>/dev/null; then
    exit 0  # already running a stop hook, don't loop
fi
```

### Notification
Fires for: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`. Common use: desktop notifications.

### FileChanged
Fires when a watched file changes on disk. The matcher **both** builds the watch list (literal filenames split by `|`) **and** filters which handlers run. Example watching `.envrc`:
```json
{"matcher": ".envrc", "hooks": [{"type": "command", "command": "direnv export bash >> \"$CLAUDE_ENV_FILE\""}]}
```

### SubagentStop
Fires when a subagent finishes. Useful for aggregating subagent results.

### WorktreeCreate
Fires when a worktree is created. **Any non-zero exit fails worktree creation.** The hook must print the absolute path of the created worktree to stdout:
```bash
echo "/absolute/path/to/worktree"
```

---

## Hook handler types (quick reference)

| Type | Use when | Key fields |
|---|---|---|
| `command` | Shell script, full OS access | `command`, `async`, `shell`, `timeout` |
| `http` | Remote service, shared state | `url`, `headers`, `allowedEnvVars`, `timeout` |
| `mcp_tool` | Delegate the hook decision to a configured MCP tool | `server`, `tool`, `arguments`, `timeout` |
| `prompt` | LLM evaluation | `prompt` (use `$ARGUMENTS`), `model`, `timeout` (30s default) |
| `agent` | Multi-step tool-using verification | `prompt` (use `$ARGUMENTS`), `model`, `timeout` (60s default) |

See `references/hook-handler-types.md` for full schemas and examples.

---

## Matcher syntax (quick reference)

| Value | Interpreted as |
|---|---|
| `*`, `""`, or omitted | Match all — fires on every occurrence |
| Letters/digits/`_`/`\|` only | Exact string or `\|`-separated list: `Bash`, `Edit\|Write` |
| Any other character | JavaScript regex: `^Notebook`, `mcp__memory__.*` |

**Common mistake**: `mcp__memory` (underscores only) is evaluated as an exact string. It never matches any real tool because real tools are `mcp__memory__create_entities` etc. Use `mcp__memory__.*` (has `.*`) for regex.

---

## Exit code contract

```
Exit 0  → success; parse stdout for JSON output
Exit 2  → blocking error; ignore stdout; use stderr as the error message
Exit 1  → NON-BLOCKING error; the action proceeds anyway; stderr shown in transcript
Timeout → treated as non-blocking error
```

Not every event can be blocked. See `references/hook-event-reference.md` for the per-event blocking table.

---

## Plugin hooks configuration

Hooks ship with a plugin in `hooks/hooks.json` at the plugin root. Example:

```json
{
  "description": "Format TypeScript files after edits",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [{"type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh"}]
      }
    ]
  }
}
```

Use `${CLAUDE_PLUGIN_ROOT}` for paths to scripts bundled with the plugin. Use `${CLAUDE_PLUGIN_DATA}` for state that should survive plugin updates.

---

## References

| File | Contents |
|---|---|
| `references/hook-event-reference.md` | Current hook event catalog with schemas, blocking behavior, matcher semantics |
| `references/hook-handler-types.md` | Command, http, mcp_tool, prompt, agent — full schemas, examples, security |
| `references/hook-anti-patterns.md` | Exit code 1 trap, infinite Stop loops, shell profile pollution, partial updatedInput, and 10+ more |
| `references/hook-testing-patterns.md` | `/hooks` menu, debug log, `test_hook.sh` usage, stdin replay |

---

> *Plugin-Dev Authoring Toolkit by [Viktor Bezdek](https://github.com/viktorbezdek) — licensed under MIT.*

Files in this skill

  • SKILL.md8.1 KB
  • evals/evals.json1.3 KB
  • evals/trigger-evals.json3.2 KB
  • references/hook-anti-patterns.md9.4 KB
  • references/hook-event-reference.md13.2 KB
  • references/hook-handler-types.md10.2 KB
  • references/hook-testing-patterns.md5.6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.