When working with Claude Code, running formatters (mix format, prettier, rubocop) and linters manually after every AI edit gets tedious quickly.
Claude Code includes a Hooks mechanism that runs specified shell commands automatically at key points in the workflow—such as immediately after a tool finishes editing a file.
Here is how to set them up and how I use them in my projects.
Configuration Scopes
Hooks are configured in Claude Code’s settings JSON files. There are three levels of scope:
~/.claude/settings.json— Global user settings (applied across all projects).claude/settings.json— Project settings (committed to git, shared with teammates).claude/settings.local.json— Local project overrides (gitignored, personal machine only)
Basic Setup: Format on File Edit
To run a formatter like mix format every time Claude Code modifies a file, configure a PostToolUse hook with a matcher for the Edit tool:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "mix format"
}
]
}
]
}
}
With this in .claude/settings.json, you no longer have to worry about unformatted code making it into your diffs.
Trigger Points and Matchers
Claude Code provides several trigger points:
PreToolUse: Runs before a tool executes. Useful for logging or validation.PostToolUse: Runs after a tool completes successfully. Best for formatters and linters.Stop: Runs when Claude finishes responding to the prompt.Notification: Triggers when Claude sends a notification.
The matcher field controls which tool triggers the hook:
"matcher": "Edit": Triggers only on file edits."matcher": "Edit|Write": Regex matching multiple tools (e.g., both partial edits and whole file writes)."matcher": "Bash": Triggers on command executions.- Omitted matcher: Runs on every event for that trigger point.
Multi-Command Example
You can chain multiple commands under a single trigger. For example, running both a formatter and a strict linter on every file modification, plus logging shell commands:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "mix format"
},
{
"type": "command",
"command": "mix credo --strict"
}
]
}
],
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "echo \"[$(date)] Running: $CLAUDE_TOOL_INPUT\" >> .claude/command_log.txt"
}
]
}
]
}
}
Summary
Setting up a simple PostToolUse hook for your project’s default formatter takes two minutes and removes the friction of manual cleanup after AI-assisted edits. If you work across multiple languages, putting language-agnostic preferences in user settings and project-specific linters in .claude/settings.json keeps things clean and predictable.