Agent skill
hooks-patterns
Hook recipes and working examples — plugin hooks, frontmatter hooks in skills/agents/commands, prompt-based LLM hooks, and complete code examples in Python and Node.js. Use when building hook scripts, integrating hooks into plugins, implementing prompt-based hooks, or looking for hook configuration patterns.
Install this agent skill to your Project
npx add-skill https://github.com/Jamie-BitFlight/claude_skills/tree/main/plugins/plugin-creator/skills/hooks-patterns
SKILL.md
Claude Code Hooks — Patterns & Examples (January 2026)
Working examples and recipes for building hooks. For hook system fundamentals, activate Skill(skill: "plugin-creator:hooks-core-reference"). For JSON I/O schemas, activate Skill(skill: "plugin-creator:hooks-io-api").
Plugin Hooks
Plugins can provide hooks that integrate with user and project hooks. For complete plugin documentation including plugin.json schema, directory structure, and component integration, see Skill(skill: "plugin-creator:claude-plugins-reference-2026").
How Plugin Hooks Work
- Plugin hooks defined in
hooks/hooks.jsonor custom path viahooksfield in plugin.json - When plugin enabled, its hooks merge with user and project hooks
- Multiple hooks from different sources can respond to same event
- Plugin hooks run alongside custom hooks in parallel
Plugin Hook Configuration
Hooks can be configured in hooks/hooks.json or inline in plugin.json:
{
"description": "Automatic code formatting",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
"timeout": 30
}
]
}
]
}
}
Reference in plugin.json:
{
"name": "my-plugin",
"hooks": "./hooks/hooks.json"
}
Or define inline:
{
"name": "my-plugin",
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh"
}
]
}
]
}
}
Plugin Environment Variables
${CLAUDE_PLUGIN_ROOT}: Absolute path to the plugin directory${CLAUDE_PROJECT_DIR}: Project root directory- All standard environment variables available
Hooks in Skills, Agents, and Slash Commands
Hooks can be defined in frontmatter. These are scoped to the component's lifecycle. For complete skill documentation, see Skill(skill: "plugin-creator:claude-skills-overview-2026").
Supported events: All hook events are supported in skill and agent frontmatter. The most common for subagents are PreToolUse, PostToolUse, and Stop (which is automatically converted to SubagentStop in agent context).
Skill Example
---
description: Perform operations with security checks
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/security-check.sh"
---
Agent Example
---
description: Review code changes
hooks:
PostToolUse:
- matcher: "Edit|Write"
hooks:
- type: command
command: "./scripts/run-linter.sh"
---
once Option
Set once: true to run hook only once per session. After first successful execution, hook is removed.
hooks:
PreToolUse:
- matcher: "Bash"
hooks:
- type: command
command: "./scripts/one-time-setup.sh"
once: true
Note: Only supported for skills and slash commands, not agents.
Prompt-Based Hooks
LLM-evaluated decisions using a fast model (Haiku). Also known as "agent hooks" for complex verification tasks.
How Prompt-Based Hooks Work
- Send the hook input and your prompt to Haiku
- The LLM responds with structured JSON containing a decision
- Claude Code processes the decision automatically
Configuration
{
"type": "prompt",
"prompt": "Evaluate if Claude should stop: <USER_ARGUMENTS>. Check if all tasks are complete.",
"timeout": 30
}
Note:
<USER_ARGUMENTS>above represents the`$ARGUMENTS`placeholder — replace it with the literal token$ARGUMENTSin your actual hook configuration. The placeholder is used here because skill files undergo argument substitution at load time.
Alternatively, use "type": "agent" for complex verification tasks that require tool access.
| Field | Required | Description |
|---|---|---|
type |
Yes | "prompt" for LLM evaluation, "agent" for tools |
prompt |
Yes | Prompt text sent to LLM |
timeout |
No | Seconds (default: 30 for prompt, 60 for agent) |
Response Schema
The LLM must respond with JSON:
{
"ok": true,
"reason": "Explanation for the decision"
}
| Field | Type | Description |
|---|---|---|
ok |
boolean | true allows the action, false prevents it |
reason |
string | Required when ok is false. Shown to Claude |
The ARGUMENTS Placeholder
Use `$ARGUMENTS` in prompt to include hook input JSON. If omitted, input is appended to the prompt.
Example: Intelligent Stop Hook
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "You are evaluating whether Claude should stop working. Context: <USER_ARGUMENTS>\n\nAnalyze the conversation and determine if:\n1. All user-requested tasks are complete\n2. Any errors need to be addressed\n3. Follow-up work is needed\n\nRespond with JSON: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"your explanation\"} to continue working.",
"timeout": 30
}
]
}
]
}
}
Example: SubagentStop Validation
Note:
<USER_ARGUMENTS>above represents the`$ARGUMENTS`placeholder — replace it with the literal token$ARGUMENTSin your actual hook configuration. The placeholder is used here because skill files undergo argument substitution at load time.
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Evaluate if this subagent should stop. Input: <USER_ARGUMENTS>\n\nCheck if:\n- The subagent completed its assigned task\n- Any errors occurred that need fixing\n- Additional context gathering is needed\n\nReturn: {\"ok\": true} to allow stopping, or {\"ok\": false, \"reason\": \"explanation\"} to continue."
}
]
}
]
}
}
Best Use Cases
| Event | Use Case |
|---|---|
Stop |
Intelligent task completion detection |
SubagentStop |
Verify subagent completed task |
UserPromptSubmit |
Context-aware prompt validation |
PreToolUse |
Complex permission decisions |
PermissionRequest |
Intelligent allow/deny dialogs |
Comparison with Command Hooks
| Feature | Command Hooks | Prompt Hooks |
|---|---|---|
| Execution | Runs bash script | Queries LLM |
| Decision logic | You implement in code | LLM evaluates context |
| Setup complexity | Requires script file | Configure prompt only |
| Context awareness | Limited to script | Natural language understanding |
| Performance | Fast (local) | Slower (API call) |
| Use case | Deterministic rules | Context-aware decisions |
Best Practices for Prompt Hooks
- Be specific in prompts - Clearly state what you want the LLM to evaluate
- Include decision criteria - List the factors the LLM should consider
- Test your prompts - Verify the LLM makes correct decisions for your use cases
- Set appropriate timeouts - Default is 30 seconds, adjust if needed
- Use for complex decisions - Bash hooks are better for simple, deterministic rules
Code Examples
Python: Bash Command Validation (Exit Code)
#!/usr/bin/env python3
import json
import re
import sys
# Define validation rules as (regex pattern, message) tuples
VALIDATION_RULES = [
(
r"\bgrep\b(?!.*\|)",
"Use 'rg' (ripgrep) instead of 'grep' for better performance",
),
(
r"\bfind\s+\S+\s+-name\b",
"Use 'rg --files -g pattern' instead of 'find -name'",
),
]
def validate_command(command: str) -> list[str]:
issues = []
for pattern, message in VALIDATION_RULES:
if re.search(pattern, command):
issues.append(message)
return issues
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")
if tool_name != "Bash" or not command:
sys.exit(0)
issues = validate_command(command)
if issues:
for message in issues:
print(f"\u2022 {message}", file=sys.stderr)
# Exit code 2 blocks tool call and shows stderr to Claude
sys.exit(2)
Python: UserPromptSubmit Context and Validation (JSON Output)
#!/usr/bin/env python3
import json
import sys
import re
import datetime
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
prompt = input_data.get("prompt", "")
# Check for sensitive patterns
sensitive_patterns = [
(r"(?i)\b(password|secret|key|token)\s*[:=]", "Prompt contains potential secrets"),
]
for pattern, message in sensitive_patterns:
if re.search(pattern, prompt):
# Use JSON output to block with a specific reason
output = {
"decision": "block",
"reason": f"Security policy violation: {message}. Please rephrase without sensitive information."
}
print(json.dumps(output))
sys.exit(0)
# Add current time to context
context = f"Current time: {datetime.datetime.now()}"
print(context)
# Equivalent JSON approach:
# print(json.dumps({
# "hookSpecificOutput": {
# "hookEventName": "UserPromptSubmit",
# "additionalContext": context,
# },
# }))
sys.exit(0)
Python: UserPromptSubmit Conditional Skill Invocation (Two-Layer Pattern)
The two-layer pattern separates evaluation from execution: the hook script wraps the prompt
with lightweight evaluation instructions and emits them as additionalContext. Claude then
evaluates the prompt inline — proceeding immediately for clear prompts, or invoking a skill
only when the prompt is vague. This keeps skill-load overhead off the common (clear) path.
Token overhead: Clear prompts — ~189 tokens (evaluation wrapper only). Vague prompts — 189 tokens + skill load. ~31% reduction vs. embedding evaluation logic in the hook directly (prompt-improver v0.4.0, 2026-02-14).
#!/usr/bin/env python3
import json
import sys
input_data = json.load(sys.stdin)
original_prompt = input_data.get("prompt", "")
# bypass: strip * prefix and skip evaluation
if original_prompt.startswith("*"):
print(original_prompt[1:].lstrip())
sys.exit(0)
# bypass: slash commands and memorize pass through unchanged
if original_prompt.startswith("/") or original_prompt.startswith("#"):
print(original_prompt)
sys.exit(0)
# ~189 tokens; instructs Claude to evaluate clarity,
# invoke skill only when vague
evaluation_context = (
f"Evaluate the following prompt for clarity and specificity.\n"
f"...\n"
f"PROCEED IMMEDIATELY if the prompt is clear and specific.\n"
f"If vague: use Skill(skill='your-plugin:your-skill') to clarify before proceeding.\n"
f"\nUser prompt: {original_prompt}"
)
output = {
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": evaluation_context,
}
}
print(json.dumps(output))
sys.exit(0)
Hook configuration (hooks.json):
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 /path/to/your-hook.py"
}
]
}
]
}
}
Skill contract: The skill invoked via Skill(skill='your-plugin:your-skill') must assume
the hook has already evaluated the prompt for clarity. The skill must not re-evaluate whether
the prompt is vague — that decision has already been made by the hook. The skill should
proceed directly to its task (research, clarifying questions, enrichment, or any other
domain-specific work). Re-evaluating clarity in the skill defeats the two-layer separation
and doubles the token overhead for vague prompts.
Python: PreToolUse Auto-Approval (JSON Output)
#!/usr/bin/env python3
import json
import sys
try:
input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
sys.exit(1)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
# Auto-approve file reads for documentation files
if tool_name == "Read":
file_path = tool_input.get("file_path", "")
if file_path.endswith((".md", ".mdx", ".txt", ".json")):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "Documentation file auto-approved"
},
"suppressOutput": True
}
print(json.dumps(output))
sys.exit(0)
# Let normal permission flow proceed
sys.exit(0)
Node.js: SessionStart Context Injection
#!/usr/bin/env node
const output = {
hookSpecificOutput: {
hookEventName: "SessionStart",
additionalContext: `<project-context>
Environment: ${process.env.NODE_ENV || "development"}
Node version: ${process.version}
Working directory: ${process.cwd()}
</project-context>`,
},
};
console.log(JSON.stringify(output));
Configuration Snippet Examples
Code Formatting (PostToolUse)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "prettier --write \"$CLAUDE_PROJECT_DIR\"/**/*.{js,ts,json}"
}
]
}
]
}
}
File Protection (PreToolUse)
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "./scripts/check-protected-files.sh"
}
]
}
]
}
}
Custom Notifications
{
"hooks": {
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/permission-alert.sh"
}
]
},
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "/path/to/idle-notification.sh"
}
]
}
]
}
}
Task Verification (Stop)
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "Verify task completion. Check edge cases. Return {\"ok\": true} or {\"ok\": false, \"reason\": \"...\"}."
}
]
}
]
}
}
Sources
- Hooks Reference (accessed 2026-01-28)
- Hooks Guide
- Settings Reference
- Plugin Components Reference
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
ccc
This skill should be used when code search is needed (whether explicitly requested or as part of completing a task), when indexing the codebase after changes, or when the user asks about ccc, cocoindex-code, or the codebase index. Trigger phrases include 'search the codebase', 'find code related to', 'update the index', 'ccc', 'cocoindex-code'.
agent-browser
Browser automation CLI for AI agents. Use when the user needs to interact with websites, including navigating pages, filling forms, clicking buttons, taking screenshots, extracting data, testing web apps, or automating any browser task. Triggers include requests to "open a website", "fill out a form", "click a button", "take a screenshot", "scrape data from a page", "test this web app", "login to a site", "automate browser actions", or any task requiring programmatic web interaction.
delegate
Quick delegation template for sub-agent prompts. Use when assigning work to a sub-agent, before invoking the Agent tool, or when preparing prompts for specialized agents. Provides the WHERE-WHAT-WHY framework. For comprehensive delegation guidance, activate the agent-orchestration how-to-delegate skill.
swarm-spawning
Spawn agents and teammates in Claude Code swarms. Use when choosing between subagents vs teammates, selecting agent types (Explore, Plan, general-purpose, plugin agents), configuring spawn backends (in-process, tmux, iterm2), or setting environment variables for spawned agents.
knowledge-explorer
Manage the research/ knowledge base (KB) of tool and library research entries. Use when browsing KB topics, adding new research entries, updating existing entries with dated revisions, fetching GitHub repo metadata into a draft KB entry, or migrating old-format entries to skill-spec frontmatter. Triggers on tasks like "what do we have on X", "add this to the KB", "update the KB entry for Y", "fetch github info for owner/repo", or "migrate old entries".
design-anti-patterns
Enforce anti-AI UI design rules based on the Uncodixfy methodology. Use when generating HTML, CSS, React, Vue, Svelte, or any frontend UI code. Prevents "Codex UI" — the generic AI aesthetic of soft gradients, floating panels, oversized rounded corners, glassmorphism, hero sections in dashboards, and decorative copy. Applies constraints from Linear/Raycast/Stripe/GitHub design philosophy: functional, honest, human-designed interfaces. Triggers on: UI generation, dashboard building, frontend component creation, CSS styling, landing page design, or any task producing visual interface code.
Didn't find tool you were looking for?