Claude Code Automation: Hooks, Skills, and Rules Explained
You've been using Claude Code for a while. It reads your files, writes code, runs commands. It's good. But you're still doing things manually that could happen automatically — formatting code after every edit, checking for dangerous commands before they run, following the same multi-step deploy process over and over.
Claude Code has three systems that fix this: hooks, skills, and rules. Only 12% of setups in our analysis use hooks, but those that do score 6.8/10 on average versus 2.1 without. The gap isn't small — and the setup isn't hard.
Let's start with something concrete.
Your first hook: auto-format every file Claude touches
Here's the problem. Claude edits a TypeScript file. The code works, but the formatting is off — wrong indentation, missing semicolons, whatever your Prettier config enforces. You ask Claude to fix the formatting. That's a wasted turn.
Instead, let's make formatting happen automatically after every edit.
Create this file at .claude/hooks/format-on-save.sh:
#!/bin/bash
# Runs after Claude edits or creates a file
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.input.file_path // empty')
if [ -z "$FILE" ]; then
exit 0
fi
# Format based on file type
case "$FILE" in
*.ts|*.tsx|*.js|*.jsx)
npx prettier --write "$FILE" 2>/dev/null
;;
*.py)
ruff format "$FILE" 2>/dev/null
;;
*.go)
gofmt -w "$FILE" 2>/dev/null
;;
esac
Make it executable:
chmod +x .claude/hooks/format-on-save.sh
Now register it in .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"command": ".claude/hooks/format-on-save.sh"
}
]
}
}
That's it. Next time Claude edits any file, Prettier (or ruff, or gofmt) runs automatically. No extra prompts, no manual cleanup.
Try it. Ask Claude to create a simple function in any TypeScript file. Watch the output — the file gets formatted automatically after the edit. You never have to think about formatting again.
What exactly are hooks, skills, and rules?
Before we go further, let's get the mental model straight. These three things confuse almost everyone at first, so here's the short version:
- Hooks run code before or after every tool call. They're automatic — you don't trigger them, they just fire. Think "event listeners."
- Skills are slash commands you type.
/deploy,/review,/ticket PROJ-123. They package a multi-step workflow you can trigger by name. - Rules are instructions that load automatically based on which files Claude is working on. When Claude touches a
.tsxfile, your frontend rules appear. When it touches SQL, your database rules appear.
| Feature | When it runs | What it does | You trigger it? |
|---|---|---|---|
| Hooks | Before/after every tool call | Intercept, block, or transform | No — automatic |
| Skills | When you type a slash command | Run a multi-step workflow | Yes — you invoke it |
| Rules | When Claude touches matching files | Inject file-specific instructions | No — automatic |
Now let's look at each one in detail.
Hooks: the safety net and the autopilot
A hook is a shell script that Claude Code runs at a specific moment. There are four types:
- PreToolUse — runs before a tool call. Can block it.
- PostToolUse — runs after a tool call. Can transform the output.
- Stop — runs when Claude finishes its turn.
- SessionStart — runs once when you start a session.
You already saw PostToolUse with the formatter. Let's look at the most important one: a safety guard.
The PreToolUse safety guard
This hook catches dangerous commands before they execute. Copy this to .claude/hooks/pre-tool-guard.sh:
#!/bin/bash
# Block destructive shell commands
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool')
COMMAND=$(echo "$INPUT" | jq -r '.input.command // empty')
if [ "$TOOL" = "Bash" ]; then
if echo "$COMMAND" | grep -qE 'rm -rf /|git push --force|git reset --hard|DROP TABLE'; then
echo '{"blocked": true, "reason": "Destructive command blocked by safety guard"}'
exit 0
fi
fi
echo '{"blocked": false}'
chmod +x .claude/hooks/pre-tool-guard.sh
Register it in settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"command": ".claude/hooks/pre-tool-guard.sh"
}
]
}
}
Now if Claude ever tries to run rm -rf / or git push --force, the hook intercepts it, returns blocked: true, and Claude sees the reason. The command never executes.
You can test it right now without Claude:
echo '{"tool":"Bash","input":{"command":"rm -rf /"}}' | .claude/hooks/pre-tool-guard.sh
You should see {"blocked": true, "reason": "Destructive command blocked by safety guard"}. That's your safety net working.
SessionStart: give Claude context from the start
This hook runs once when you open Claude Code. It's great for injecting context that helps Claude orient itself:
#!/bin/bash
# .claude/hooks/session-start.sh
echo "## Current Git State"
echo "Branch: $(git branch --show-current)"
echo "Last commit: $(git log --oneline -1)"
echo "Modified files:"
git status --short
echo ""
echo "## Recent TODOs"
grep -r "TODO\|FIXME\|HACK" src/ --include="*.ts" -l 2>/dev/null | head -10
Every session starts with Claude knowing which branch you're on, what the last commit was, and which files have open TODOs. No more "what branch am I on?" as your first question.
Skills: your personal slash commands
Skills are where things get fun. You write a markdown file that describes a workflow, and it becomes a slash command you can type any time.
Your first skill: a deploy command
Create .claude/skills/deploy.md:
---
name: deploy
description: Deploy the current branch to staging or production
---
# Deploy Workflow
1. Run `npm run build` to verify the build passes
2. Run `npm run test` to confirm all tests pass
3. Check the current branch: `git branch --show-current`
4. If on `main`:
- Deploy to production: `railway up --service production`
- Verify health check: `curl -s https://api.example.com/health`
5. If on any other branch:
- Deploy to staging: `railway up --service staging`
- Output the staging URL for review
Always report the deployment status and the URL.
Now type /deploy in Claude Code. Claude builds, tests, checks the branch, deploys to the right environment, and verifies the health check. One command instead of five manual steps.
A code review skill
This one is practical for daily use. Save as .claude/skills/review.md:
---
name: review
description: Review staged changes before committing
---
# Code Review
1. Run `git diff --staged` to see all changes
2. For each file, check:
- Error handling: are async operations wrapped in try/catch?
- Security: any hardcoded secrets, SQL injection, or XSS?
- Types: any `any` that should be specific?
- Tests: do modified functions have test updates?
3. AUTO-FIX mechanical issues (formatting, missing types, obvious bugs)
4. Report CRITICAL issues (must fix) and INFORMATIONAL (worth considering)
5. If no critical issues: suggest a commit message
Type /review before committing and Claude checks your work. Mechanical issues get fixed automatically. Ambiguous decisions get flagged for you to decide.
Skills that take arguments
Skills can accept input. Save as .claude/skills/ticket.md:
---
name: ticket
description: Start work on a specific ticket
---
The user will provide a ticket ID (e.g., `/ticket PROJ-123`).
1. Fetch ticket details from the issue tracker
2. Create a new branch: `git checkout -b feat/PROJ-123-description`
3. Read related files mentioned in the ticket
4. Propose an implementation plan before writing code
5. Wait for approval before proceeding
Type /ticket PROJ-123 and Claude reads the ticket, creates a branch, finds the relevant files, and proposes a plan — all before writing a single line of code.
Rules: instructions that load at the right time
You already saw rules in the setup guide. Here's why they matter for automation: they keep Claude's context clean.
Imagine you have 200 lines of frontend conventions, 150 lines of backend rules, and 100 lines of database standards. If you put all 450 lines in CLAUDE.md, Claude loads everything on every single interaction — even when you're only editing a CSS file.
With rules, Claude loads the frontend conventions only when touching .tsx files, database rules only when editing migrations, and security warnings only when near sensitive files.
Here's a security rule that matters:
<!-- .claude/rules/security.md -->
---
globs: [".env*", "*.pem", "*.key", "*secret*", "*credential*"]
---
- NEVER commit this file to version control
- NEVER log the contents of this file
- NEVER include these values in error messages or API responses
- If this file needs modification, explain what changes are needed but do not display current values
When Claude touches any file matching those patterns, these rules appear automatically. When it's working on unrelated code, they stay out of the way.
The cheat sheet: when to use what
This is the question everyone asks. Here's how to decide:
Use a hook when the action should happen every time, without anyone thinking about it. Formatting on save. Blocking dangerous commands. Logging every tool call. If a human shouldn't have to remember to do it, it's a hook.
Use a skill when you have a multi-step process you repeat regularly. Deploying. Reviewing code. Starting a new ticket. Writing a migration. If you'd write a checklist for it, it's a skill.
Use a rule when the instructions depend on what file Claude is working on. Frontend conventions for .tsx files. SQL safety for migrations. Security warnings for .env files. If the advice is "when working on X, follow these guidelines," it's a rule.
Combining all three: a real-world example
Here's how hooks, skills, and rules work together on a database migration workflow:
-
Rule (
.claude/rules/database.md) — when Claude touches migration files, it automatically loads your conventions: parameterized queries, rollback requirements, naming patterns. -
Hook (PreToolUse on
Bash) — if Claude tries to run a migration command, the hook checks that a rollback file exists first. Blocks the command if there's no rollback. -
Skill (
/migrate) — a slash command that creates the migration file from a template, generates the matching rollback, runs it against a test database, and reports the result.
The rule provides the knowledge. The hook enforces the constraint. The skill packages the workflow. Each piece does one job well.
When things don't work
If your hooks, skills, or rules aren't firing, here's how to debug:
# Run Claude in verbose mode to see hook execution
claude --verbose
# Test a hook independently
echo '{"tool":"Bash","input":{"command":"rm -rf /"}}' | .claude/hooks/pre-tool-guard.sh
The usual suspects:
- Hook script missing execute permissions. Fix with
chmod +x .claude/hooks/your-hook.sh - Glob pattern not matching. The pattern
**/api/**/*.tsis different fromapi/**/*.ts. Test yours against actual file paths. - Skill not found. Make sure it's in
.claude/skills/with the correctname:in frontmatter.
What you've built
If you followed along, you now have:
- A safety guard that blocks destructive commands before they run
- An auto-formatter that cleans up every file Claude touches
- A deploy skill that packages your entire deploy workflow into one command
- A review skill that checks your code before you commit
- Rules that inject the right conventions at the right time
Most Claude Code users never get here. You just built a custom development environment that works for you automatically.
Ready for more? Here's where to go next:
- Connect Claude to your tools — MCP servers that let Claude query your database, search GitHub, and pull Sentry errors
- Advanced workflow patterns — subagents, parallel execution, and the debugging patterns that power users rely on
- Score your setup to see exactly where your automation stands
Frequently Asked Questions
Do hooks slow things down?
A simple grep-based safety guard adds 10-50 milliseconds per tool call — you won't notice. But a PostToolUse hook that runs Prettier adds 200-500ms per edit. Keep hooks fast. If a hook needs to do heavy work, background the process so Claude doesn't wait for it.
Can I share hooks across all my projects?
Yes. Put shared hooks in ~/.claude/hooks/ and reference them with absolute paths in your global ~/.claude/settings.json. Project-specific hooks go in .claude/hooks/ with relative paths in the project settings.
What happens if a hook crashes?
Claude Code catches hook errors and proceeds as if the hook didn't exist — the tool call runs normally. The error shows up in verbose mode. This is a safety-first design: a broken hook never prevents Claude from working. But test your hooks thoroughly — a broken safety guard that fails silently is worse than no guard at all.
Can one skill call another skill?
Not directly. There's no /deploy calling /review mechanism. If you need a composed workflow, build a single skill that includes all the steps. Alternatively, use a hook to trigger additional logic after a skill completes.
How do rules interact with CLAUDE.md?
Both get loaded into context, and Claude follows all of them. If a rule contradicts something in CLAUDE.md, the more specific instruction usually wins. But avoid contradictions in the first place — use CLAUDE.md for project-wide instructions and rules for file-specific overrides.
