Initial commit
This commit is contained in:
332
skills/working-with-claude-code/references/hooks-guide.md
Normal file
332
skills/working-with-claude-code/references/hooks-guide.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# Get started with Claude Code hooks
|
||||
|
||||
> Learn how to customize and extend Claude Code's behavior by registering shell commands
|
||||
|
||||
Claude Code hooks are user-defined shell commands that execute at various points
|
||||
in Claude Code's lifecycle. Hooks provide deterministic control over Claude
|
||||
Code's behavior, ensuring certain actions always happen rather than relying on
|
||||
the LLM to choose to run them.
|
||||
|
||||
<Tip>
|
||||
For reference documentation on hooks, see [Hooks reference](/en/docs/claude-code/hooks).
|
||||
</Tip>
|
||||
|
||||
Example use cases for hooks include:
|
||||
|
||||
* **Notifications**: Customize how you get notified when Claude Code is awaiting
|
||||
your input or permission to run something.
|
||||
* **Automatic formatting**: Run `prettier` on .ts files, `gofmt` on .go files,
|
||||
etc. after every file edit.
|
||||
* **Logging**: Track and count all executed commands for compliance or
|
||||
debugging.
|
||||
* **Feedback**: Provide automated feedback when Claude Code produces code that
|
||||
does not follow your codebase conventions.
|
||||
* **Custom permissions**: Block modifications to production files or sensitive
|
||||
directories.
|
||||
|
||||
By encoding these rules as hooks rather than prompting instructions, you turn
|
||||
suggestions into app-level code that executes every time it is expected to run.
|
||||
|
||||
<Warning>
|
||||
You must consider the security implication of hooks as you add them, because hooks run automatically during the agent loop with your current environment's credentials.
|
||||
For example, malicious hooks code can exfiltrate your data. Always review your hooks implementation before registering them.
|
||||
|
||||
For full security best practices, see [Security Considerations](/en/docs/claude-code/hooks#security-considerations) in the hooks reference documentation.
|
||||
</Warning>
|
||||
|
||||
## Hook Events Overview
|
||||
|
||||
Claude Code provides several hook events that run at different points in the
|
||||
workflow:
|
||||
|
||||
* **PreToolUse**: Runs before tool calls (can block them)
|
||||
* **PostToolUse**: Runs after tool calls complete
|
||||
* **UserPromptSubmit**: Runs when the user submits a prompt, before Claude processes it
|
||||
* **Notification**: Runs when Claude Code sends notifications
|
||||
* **Stop**: Runs when Claude Code finishes responding
|
||||
* **SubagentStop**: Runs when subagent tasks complete
|
||||
* **PreCompact**: Runs before Claude Code is about to run a compact operation
|
||||
* **SessionStart**: Runs when Claude Code starts a new session or resumes an existing session
|
||||
* **SessionEnd**: Runs when Claude Code session ends
|
||||
|
||||
Each event receives different data and can control Claude's behavior in
|
||||
different ways.
|
||||
|
||||
## Quickstart
|
||||
|
||||
In this quickstart, you'll add a hook that logs the shell commands that Claude
|
||||
Code runs.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Install `jq` for JSON processing in the command line.
|
||||
|
||||
### Step 1: Open hooks configuration
|
||||
|
||||
Run the `/hooks` [slash command](/en/docs/claude-code/slash-commands) and select
|
||||
the `PreToolUse` hook event.
|
||||
|
||||
`PreToolUse` hooks run before tool calls and can block them while providing
|
||||
Claude feedback on what to do differently.
|
||||
|
||||
### Step 2: Add a matcher
|
||||
|
||||
Select `+ Add new matcher…` to run your hook only on Bash tool calls.
|
||||
|
||||
Type `Bash` for the matcher.
|
||||
|
||||
<Note>You can use `*` to match all tools.</Note>
|
||||
|
||||
### Step 3: Add the hook
|
||||
|
||||
Select `+ Add new hook…` and enter this command:
|
||||
|
||||
```bash theme={null}
|
||||
jq -r '"\(.tool_input.command) - \(.tool_input.description // "No description")"' >> ~/.claude/bash-command-log.txt
|
||||
```
|
||||
|
||||
### Step 4: Save your configuration
|
||||
|
||||
For storage location, select `User settings` since you're logging to your home
|
||||
directory. This hook will then apply to all projects, not just your current
|
||||
project.
|
||||
|
||||
Then press Esc until you return to the REPL. Your hook is now registered!
|
||||
|
||||
### Step 5: Verify your hook
|
||||
|
||||
Run `/hooks` again or check `~/.claude/settings.json` to see your configuration:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 6: Test your hook
|
||||
|
||||
Ask Claude to run a simple command like `ls` and check your log file:
|
||||
|
||||
```bash theme={null}
|
||||
cat ~/.claude/bash-command-log.txt
|
||||
```
|
||||
|
||||
You should see entries like:
|
||||
|
||||
```
|
||||
ls - Lists files and directories
|
||||
```
|
||||
|
||||
## More Examples
|
||||
|
||||
<Note>
|
||||
For a complete example implementation, see the [bash command validator example](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py) in our public codebase.
|
||||
</Note>
|
||||
|
||||
### Code Formatting Hook
|
||||
|
||||
Automatically format TypeScript files after editing:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Markdown Formatting Hook
|
||||
|
||||
Automatically fix missing language tags and formatting issues in markdown files:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Create `.claude/hooks/markdown_formatter.py` with this content:
|
||||
|
||||
````python theme={null}
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Markdown formatter for Claude Code output.
|
||||
Fixes missing language tags and spacing issues while preserving code content.
|
||||
"""
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
|
||||
def detect_language(code):
|
||||
"""Best-effort language detection from code content."""
|
||||
s = code.strip()
|
||||
|
||||
# JSON detection
|
||||
if re.search(r'^\s*[{\[]', s):
|
||||
try:
|
||||
json.loads(s)
|
||||
return 'json'
|
||||
except:
|
||||
pass
|
||||
|
||||
# Python detection
|
||||
if re.search(r'^\s*def\s+\w+\s*\(', s, re.M) or \
|
||||
re.search(r'^\s*(import|from)\s+\w+', s, re.M):
|
||||
return 'python'
|
||||
|
||||
# JavaScript detection
|
||||
if re.search(r'\b(function\s+\w+\s*\(|const\s+\w+\s*=)', s) or \
|
||||
re.search(r'=>|console\.(log|error)', s):
|
||||
return 'javascript'
|
||||
|
||||
# Bash detection
|
||||
if re.search(r'^#!.*\b(bash|sh)\b', s, re.M) or \
|
||||
re.search(r'\b(if|then|fi|for|in|do|done)\b', s):
|
||||
return 'bash'
|
||||
|
||||
# SQL detection
|
||||
if re.search(r'\b(SELECT|INSERT|UPDATE|DELETE|CREATE)\s+', s, re.I):
|
||||
return 'sql'
|
||||
|
||||
return 'text'
|
||||
|
||||
def format_markdown(content):
|
||||
"""Format markdown content with language detection."""
|
||||
# Fix unlabeled code fences
|
||||
def add_lang_to_fence(match):
|
||||
indent, info, body, closing = match.groups()
|
||||
if not info.strip():
|
||||
lang = detect_language(body)
|
||||
return f"{indent}```{lang}\n{body}{closing}\n"
|
||||
return match.group(0)
|
||||
|
||||
fence_pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'
|
||||
content = re.sub(fence_pattern, add_lang_to_fence, content)
|
||||
|
||||
# Fix excessive blank lines (only outside code fences)
|
||||
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||
|
||||
return content.rstrip() + '\n'
|
||||
|
||||
# Main execution
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
file_path = input_data.get('tool_input', {}).get('file_path', '')
|
||||
|
||||
if not file_path.endswith(('.md', '.mdx')):
|
||||
sys.exit(0) # Not a markdown file
|
||||
|
||||
if os.path.exists(file_path):
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
formatted = format_markdown(content)
|
||||
|
||||
if formatted != content:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(formatted)
|
||||
print(f"✓ Fixed markdown formatting in {file_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error formatting markdown: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
````
|
||||
|
||||
Make the script executable:
|
||||
|
||||
```bash theme={null}
|
||||
chmod +x .claude/hooks/markdown_formatter.py
|
||||
```
|
||||
|
||||
This hook automatically:
|
||||
|
||||
* Detects programming languages in unlabeled code blocks
|
||||
* Adds appropriate language tags for syntax highlighting
|
||||
* Fixes excessive blank lines while preserving code content
|
||||
* Only processes markdown files (`.md`, `.mdx`)
|
||||
|
||||
### Custom Notification Hook
|
||||
|
||||
Get desktop notifications when Claude needs input:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hooks": {
|
||||
"Notification": [
|
||||
{
|
||||
"matcher": "",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "notify-send 'Claude Code' 'Awaiting your input'"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### File Protection Hook
|
||||
|
||||
Block edits to sensitive files:
|
||||
|
||||
```json theme={null}
|
||||
{
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Learn more
|
||||
|
||||
* For reference documentation on hooks, see [Hooks reference](/en/docs/claude-code/hooks).
|
||||
* For comprehensive security best practices and safety guidelines, see [Security Considerations](/en/docs/claude-code/hooks#security-considerations) in the hooks reference documentation.
|
||||
* For troubleshooting steps and debugging techniques, see [Debugging](/en/docs/claude-code/hooks#debugging) in the hooks reference
|
||||
documentation.
|
||||
Reference in New Issue
Block a user