Plugins & MCP Beginner 9 min read

Claude Code Hooks: Automate Workflows

Learn how to trigger shell commands automatically when Claude Code events happen in your projects.

What Are Claude Code Hooks?

When I first heard about Claude Code hooks, I'll be honest — I thought it was some advanced feature I'd never use. Then I spent a Friday afternoon manually running the same three commands every time Claude generated code for me: formatting, running tests, and committing to git. That's when it clicked: hooks could automate all of this.

Think of hooks as your personal automation assistant. Every time something happens in Claude Code — like generating a new file, updating existing code, or finishing a conversation — you can automatically trigger shell commands. It's like having a helpful colleague who remembers to do all the boring stuff you always forget.

Setting Up Your First Hook

Let's start simple. I'll show you how to set up a hook that automatically formats your code whenever Claude makes changes. This was actually the first hook I ever created, and it saved me so much time.

First, you need to create a hooks configuration file in your project root:

terminal
# Create the hooks config file
touch .claude-hooks.json

Now, open that file and add your first hook configuration:

.claude-hooks.json
{
"hooks": {
"after-code-generation": [
{
"name": "format-code",
"command": "npx prettier --write .",
"description": "Auto-format generated code"
}
]
}
}

That's it for the basic setup! Now whenever Claude generates or modifies code, Prettier will automatically format it. No more inconsistent indentation or spacing issues.

Common Hook Events You Should Know

There are several events you can hook into, and understanding them will help you build better automation. Here are the ones I use most often:

after-code-generation - Runs after Claude creates or modifies code files

before-conversation-end - Triggers when you're wrapping up a coding session

after-file-save - Runs whenever any file gets saved in your project

on-error-fix - Triggers after Claude helps fix an error or bug

The key is matching your automation to your actual workflow. I learned this the hard way when I set up hooks for events that barely happened in my projects.

Building a Complete Development Workflow

Once you get comfortable with basic hooks, you can chain multiple commands together. Here's the workflow I use for most of my projects — it handles formatting, testing, and git operations automatically:

.claude-hooks.json
{
"hooks": {
"after-code-generation": [
{
"name": "format-and-lint",
"command": "npm run format && npm run lint",
"description": "Format and lint generated code"
},
{
"name": "run-tests",
"command": "npm test -- --silent",
"description": "Run tests to catch issues early",
"condition": "test-files-exist"
}
],
"before-conversation-end": [
{
"name": "git-add-commit",
"command": "git add . && git commit -m 'Claude code generation session'",
"description": "Auto-commit Claude's changes",
"prompt": true
}
]
}
}

Notice the condition and prompt properties. These give you more control over when hooks run. The condition prevents tests from running if you don't have any test files, and the prompt asks for confirmation before committing to git.

Advanced Hook Configurations

As you get more comfortable with hooks, you can add some really useful features. Here's what I discovered after using hooks for a few months:

You can pass context about what Claude generated to your shell commands using environment variables:

hook-script.sh
#!/bin/bash
# Access Claude context variables
echo "Files modified: $CLAUDE_MODIFIED_FILES"
echo "Conversation topic: $CLAUDE_CONVERSATION_TOPIC"

# Run different commands based on file types
if [[ $CLAUDE_MODIFIED_FILES == *".py"* ]]; then
python -m black .
python -m pytest --quiet
elif [[ $CLAUDE_MODIFIED_FILES == *".js"* ]]; then
npm run format
npm test
fi

You can also set up conditional logic within your JSON config:

.claude-hooks.json
{
"hooks": {
"after-code-generation": [
{
"name": "python-specific",
"command": "python -m black . && python -m pytest",
"condition": "file-extension:.py",
"timeout": 30
},
{
"name": "javascript-specific",
"command": "npm run format && npm test",
"condition": "file-extension:.js,.ts",
"timeout": 60
}
]
}
}

Troubleshooting Common Issues

I've run into my share of hook problems, and here are the solutions that actually work:

Hook Not Running?

Check your JSON syntax first — a single missing comma will break everything. Use npx claude-code validate-hooks to check.

If your commands are failing silently, add logging to see what's happening:

.claude-hooks.json
{
"name": "debug-hook",
"command": "echo 'Hook starting...' && npm test && echo 'Hook completed!'",
"verbose": true,
"log-output": true
}

For hooks that take a while to run, set appropriate timeouts and consider running them in the background:

hook config
{
"name": "slow-tests",
"command": "npm run test:integration",
"timeout": 300,
"background": true,
"notify-on-completion": true
}

Real-World Hook Examples

Here are some hooks I actually use in my projects that might inspire your own automation:

Documentation Generator: Automatically update README files when Claude adds new features:

documentation hook
{
"name": "update-docs",
"command": "npm run generate-docs && git add README.md docs/",
"condition": "new-functions-added"
}

Dependency Security Check: Scan for vulnerabilities when Claude adds new packages:

security hook
{
"name": "security-audit",
"command": "npm audit --audit-level moderate",
"condition": "package-json-modified",
"fail-on-error": true
}

Deployment Prep: Build and test everything before wrapping up a session:

deployment hook
{
"name": "deploy-check",
"command": "npm run build && npm run test:all && echo 'Ready for deployment!'",
"event": "before-conversation-end",
"prompt": "Run deployment checks?"
}

Making Hooks Part of Your Workflow

The biggest mistake I made early on was setting up too many hooks at once. Start with one or two that solve real problems you're having, then gradually add more as you see opportunities.

I recommend starting with these three hooks — they cover the most common automation needs:

1. Code formatting after generation (saves constant manual cleanup)

2. Basic tests after changes (catches issues immediately)

3. Git commit prompt at conversation end (never lose work again)

Once these become natural, you can expand into more specialized automation based on your specific projects and workflow.

Pro Tip

Create different hook configurations for different types of projects. Keep a .claude-hooks-python.json and .claude-hooks-web.json as templates you can copy.

The best part about hooks is that they work silently in the background, handling all the routine tasks that used to interrupt your flow. Set them up once, and they'll keep your projects clean and tested without you having to think about it.

Want to go deeper?

Check out more tutorials in this category, or explore the full site.