Back to all articles
13 MIN READ

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

  1. Start Simple: Begin with one hook (like security blocker) before building a full pipeline
  2. Test Offline: Run your scripts manually before registering as hooks
  3. Use jq: Parse JSON input reliably with jq
  4. Log Everything: Add logging to debug hook behavior
  5. 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:

ToolPurposeLocation
HooksAutomatic event-driven scripts.claude/settings.json
CommandsReusable workflow templates.claude/commands/
Shell ScriptsDeterministic 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.