Claude Code Workflow Automation: Hooks, Commands
By Dorian Laurenceau
๐ Last reviewed: April 24, 2026. Updated with April 2026 findings and community feedback.
Claude Code Workflow Automation
Claude Code isn't just a coding assistant-it's an automation platform. With hooks, custom commands, and shell script integrations, you can build powerful workflows that run automatically, enforce standards, and save hours every week.
This guide teaches you to automate like a power user.
1. The Event System: How Hooks Work
Hooks are scripts that run automatically when specific events occur in Claude Code. Think of them like git hooks, but for your AI assistant.
Available Events
The Event Flow
Here's how events flow through a typical interaction:
Interactive Hook Flow Visualizer
Explore how hooks work with this interactive flowchart. Click on each hook type to see code examples in both Bash and PowerShell:
Hooks are where Claude Code stops being a chat tool and becomes actual infrastructure, and the community response mirrors that shift. On r/devops and r/ClaudeAI, the enthusiastic early takes ("I automated my entire workflow!") are now tempered by the quieter late takes ("I spent a weekend debugging why my pre-commit hook fires twice"). Both are true. Hooks unlock deterministic guardrails around a non-deterministic agent โ which is exactly the combination needed for trust โ but every hook you add is a new failure mode to own.
What's worth automating, based on what practitioners actually keep: safety rails (block writes to main, enforce conventional commits, prevent .env reads), formatting and linting on PreToolUse, and a notification on long-running sessions. What's not worth automating yet: anything that modifies state irreversibly based on a model decision. The Anthropic hooks reference is clear that hooks run with your local permissions โ which means a misfiring hook can do real damage at real speed.
The honest operating rule: every hook starts life as a script you can run standalone. If it doesn't work independently, it won't work under Claude Code โ you'll just have a harder time debugging it because the failure is tangled with agent output. Test in isolation, integrate cautiously, and keep your hook scripts in version control next to the code they guard.
2. Creating Your First Hook
Hooks are registered in your settings file and point to shell scripts.
Hook Registration
Add hooks to .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/security-check.sh",
"timeout": 5000
}
]
}
]
}
}
Configuration Fields
Hook Input & Output
Your hook receives JSON on stdin:
{
"tool_name": "Bash",
"tool_input": {
"command": "rm -rf node_modules"
},
"session_id": "abc123",
"cwd": "/my-project"
}
Your hook can output JSON to provide feedback:
{
"systemMessage": "Auto-formatted 3 files",
"hookSpecificOutput": {
"additionalContext": "Current branch: feature/auth"
}
}
Exit Codes
3. Sync vs Async Hooks
Claude Code supports two execution models for hooks:
Synchronous (Default)
- โClaude waits for the hook to complete
- โHook can provide feedback via stdout
- โExit code 2 can block operations
- โUse for: Security validation, type checking
Asynchronous
- โClaude continues immediately
- โHook runs in background
- โCannot block operations or provide feedback
- โUse for: Logging, notifications, formatting
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/auto-format.sh",
"async": true
}
]
}
]
}
}
4. Essential Hook Examples
Security Blocker (PreToolUse)
Block dangerous commands before they execute:
#!/bin/bash
# .claude/hooks/security-blocker.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
# Dangerous patterns to block
DANGEROUS=(
"rm -rf /"
"rm -rf ~"
"rm -rf *"
"sudo rm"
"git push --force origin main"
"git push -f origin main"
"> /dev/sda"
)
for pattern in "${DANGEROUS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "BLOCKED: Dangerous command: $pattern" >&2
exit 2 # Exit code 2 = BLOCK
fi
done
exit 0 # Allow the command
Auto-Formatter (PostToolUse)
Automatically format code after every edit:
#!/bin/bash
# .claude/hooks/auto-format.sh
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name')
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
# Only run for Edit/Write
if [[ "$TOOL_NAME" != "Edit" && "$TOOL_NAME" != "Write" ]]; then
exit 0
fi
# Format based on file type
if [[ "$FILE_PATH" =~ \.(ts|tsx|js|jsx|json|css)$ ]]; then
npx prettier --write "$FILE_PATH" 2>/dev/null
elif [[ "$FILE_PATH" =~ \.py$ ]]; then
black "$FILE_PATH" 2>/dev/null
fi
exit 0
Git Context Injector (UserPromptSubmit)
Automatically add git context to every prompt:
#!/bin/bash
# .claude/hooks/git-context.sh
BRANCH=$(git branch --show-current 2>/dev/null || echo "not a git repo")
STAGED=$(git diff --cached --stat 2>/dev/null | tail -1 || echo "none")
UNSTAGED=$(git diff --stat 2>/dev/null | tail -1 || echo "none")
cat << EOF
{
"hookSpecificOutput": {
"additionalContext": "[Git] Branch: $BRANCH | Staged: $STAGED | Unstaged: $UNSTAGED"
}
}
EOF
exit 0
5. Custom Slash Commands
Commands are markdown files that define reusable workflows. They're like macros for Claude Code.
Command Structure
Commands live in .claude/commands/:
.claude/commands/
โโโ tech/
โ โโโ commit.md โ /tech:commit
โ โโโ pr.md โ /tech:pr
โโโ product/
โ โโโ scope.md โ /product:scope
โโโ debug/
โโโ trace.md โ /debug:trace
Command Template
# Command Name
## Purpose
[What this command does]
## Process
1. **Step 1**
[Instructions]
2. **Step 2**
[Instructions]
## Arguments
If $ARGUMENTS[0] provided: [use as X]
If no arguments: [default behavior]
## Output Format
[Expected structure]
Example: Commit Command
# Smart Commit
## Purpose
Create a well-formatted git commit following Conventional Commits.
## Process
1. **Check Status**
Run `git status` and `git diff` to understand changes.
2. **Determine Type**
- `feat`: New feature
- `fix`: Bug fix
- `refactor`: Code restructuring
- `docs`: Documentation
- `test`: Test changes
3. **Draft Message**
Format: `type(scope): description`
4. **Stage and Commit**
```bash
git add [relevant files]
git commit -m "[message]"
Arguments
If $ARGUMENTS[0] provided: Use as commit message hint
Example: /tech:commit add user auth
Output
Commit: [hash] [message] Files: [number] changed
### Example: Problem Framer Command
```markdown
# Problem Framer
## Purpose
Challenge and refine problem definitions before solution design.
## Process
1. **Capture Initial Problem**
Record the problem as stated.
2. **5 Whys Analysis**
Ask "Why?" 5 times to find root cause:
- Why 1: [First answer]
- Why 2: [Deeper]
- Why 3: [Deeper still]
- Why 4: [Getting to root]
- Why 5: [Root cause]
3. **Stakeholder Analysis**
- Who is affected?
- Who decides?
- Who benefits?
4. **Reframe**
Write: "How might we [action] for [user] so that [outcome]?"
## Output
**Original**: [as stated]
**Root Cause**: [from 5 Whys]
**Refined**: "How might we [X] for [Y] so that [Z]?"
6. Shell Scripts vs AI Agents
Not everything needs AI. Choose the right tool for the job:
The Rule of Thumb
If you can write a regex or simple conditional for it, use a shell script. If it requires "understanding" or "judgment", use AI.
Example: PR Workflow
# DETERMINISTIC (shell script): create branch, push, open PR
git checkout -b feature/user-auth
git push -u origin feature/user-auth
gh pr create --title "Add user auth" --body "..."
# INTERPRETATION (AI agent): review code quality
# โ Use Claude's code review capabilities
7. Building a Complete Workflow
Let's build a production-ready workflow that combines hooks, commands, and scripts.
The Development Workflow
Full Configuration
{
"hooks": {
"SessionStart": [
{
"matcher": ".*",
"hooks": [{
"type": "command",
"command": ".claude/hooks/session-init.sh"
}]
}
],
"UserPromptSubmit": [
{
"matcher": ".*",
"hooks": [{
"type": "command",
"command": ".claude/hooks/git-context.sh"
}]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": ".claude/hooks/security-blocker.sh",
"timeout": 5000
}]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": ".claude/hooks/auto-format.sh",
"async": true
}]
}
],
"SessionEnd": [
{
"matcher": ".*",
"hooks": [{
"type": "command",
"command": ".claude/hooks/session-handoff.sh"
}]
}
]
}
}
8. Best Practices
Hook Development Tips
- โStart Simple: Begin with one hook (like security blocker) before building a full pipeline
- โTest Offline: Run your scripts manually before registering as hooks
- โUse jq: Parse JSON input reliably with
jq - โLog Everything: Add logging to debug hook behavior
- โSet Timeouts: Prevent hung hooks from blocking Claude
Common Pitfalls
9. Advanced: Session Handoff Hook
Here's a complete session handoff hook that captures your work:
#!/bin/bash
# .claude/hooks/session-handoff.sh
# Create handoff directory
mkdir -p claudedocs/handoffs
# Get session info
DATE=$(date +%Y-%m-%d)
TIME=$(date +%H:%M)
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
# Get recent commits
COMMITS=$(git log --oneline -5 2>/dev/null || echo "No commits")
# Get changed files
CHANGED=$(git diff --name-only HEAD~5..HEAD 2>/dev/null || echo "No changes tracked")
# Create handoff file
cat > "claudedocs/handoffs/handoff-$DATE.md" << EOF
# Session Handoff - $DATE $TIME
## Branch
$BRANCH
## Recent Commits
$COMMITS
## Files Changed
$CHANGED
## Further Exploration
[To be filled by Claude or user]
## Notes
[Session-specific notes]
EOF
echo "Created handoff: claudedocs/handoffs/handoff-$DATE.md"
exit 0
Summary
You now have the tools to automate Claude Code like a power user:
| Tool | Purpose | Location |
|---|---|---|
| Hooks | Automatic event-driven scripts | .claude/settings.json |
| Commands | Reusable workflow templates | .claude/commands/ |
| Shell Scripts | Deterministic automation | .claude/hooks/*.sh |
Quick Start Checklist
- โ Create
.claude/hooks/directory - โ Add security blocker hook (PreToolUse)
- โ Add auto-formatter hook (PostToolUse, async)
- โ Create your first custom command
- โ Test with a real development task
Continue to Module 2 for hands-on exercises building production automation workflows.
This article is part of the Claude Code Ultimate Guide series. Continue your journey with Module 2: Workflow Automation for deeper practice.
Module 2 โ Structured Outputs
Learn to get reliable, formatted responses like JSON and tables.
Dorian Laurenceau
Full-Stack Developer & Learning DesignerFull-stack web developer and learning designer. I spent 4 years as a freelance full-stack developer and 4 years teaching React, JavaScript, HTML/CSS and WordPress to adult learners. Today I design learning paths in web development and AI, grounded in learning science. I founded learn-prompting.fr to make AI practical and accessible, and built the Bluff app to gamify political transparency.
Weekly AI Insights
Tools, techniques & news โ curated for AI practitioners. Free, no spam.
Free, no spam. Unsubscribe anytime.
โRelated Articles
FAQ
What are Claude Code hooks?+
Hooks are JavaScript functions that run before or after Claude uses a tool. Use them for auto-formatting, linting, security checks, notifications, or any custom logic.
Where do I configure hooks?+
Create a .claude/hooks/ directory in your project. Add JavaScript files like pre-commit.js, post-edit.js, etc. Claude will automatically run them at the appropriate times.
Can hooks prevent Claude from making changes?+
Yes, pre-hooks can abort operations by throwing errors. Use this for security gates, preventing changes to protected files, or enforcing code standards.
What's the difference between hooks and skills?+
Skills are instructions that guide Claude's behavior. Hooks are code that executes automatically when Claude performs actions. Skills = what to do; Hooks = side effects when doing it.