Claude Code Workflow Automation: Hooks, Commands & Custom Scripts
By Learnia AI Research Team
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:
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
## Next Steps
[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.