Automating Away Claude's Bad Habits with Hooks
When AI agents like Claude Code write code, they often leave trailing whitespace for no reason. Tabs or spaces? Doesn’t matter. It’s bad.
Whyyyyy?
LLMs generate trailing whitespace because they produce text token by token without visual feedback. They ’learn’ from training data containing inconsistent whitespace patterns.
Telling Claude what to do isn’t enough
I used to have something like this in my CLAUDE.md
:
NEVER EVER leave trailing whitespace at the end of lines.
But as with many things added as context in CLAUDE.md
, Claude Code can be very
inconsistent about following them. Does that happpen as the context window fills
up? I don’t know. I want something more deterministic here, though.
Holding Claude’s hand via Hooks (for Ruby)
At the beginning of July, Anthropic introduced Hooks.
Claude Code hooks are user-defined shell commands that execute automatically at specific points in Claude’s agent loop. They intercept the agent’s workflow, allowing you to add validation, transformation, or blocking logic. Hooks run with your current environment credentials and can modify or prevent actions before they complete.
I add this to ~/.claude/settings.json
:
{
"hooks": {
"PostToolUse": [
{
"matcher": "^(Edit|MultiEdit|Write)$",
"hooks": [
{
"type": "command",
"command": "jq -r 'select(.tool_name | test(\"^(Edit|MultiEdit|Write)$\")) | .tool_input.file_path | select(test(\"\\\\.(rb|rake|gemspec)$\"))' | xargs -I {} rubocop --auto-correct --only Layout/TrailingWhitespace {} 2>/dev/null"
}
]
}
]
}
}
Now Rubocop
cleans things up if needed after every Ruby file edit!
OK, but why Rubocop and not just sed
?
I do it this way because I usually write Ruby and if I did this with, say, regexes and did it for every file Claude edits I could run into trouble!
Generic whitespace removal with sed
or regex risks corrupting binary files,
breaking Markdown line breaks, or damaging whitespace-sensitive formats (e.g.,
Python). Language-specific tools understand—they know when trailing spaces
matter and when they don’t.
RuboCop’s Layout/TrailingWhitespace
cop does one thing. It removes trailing
whitespace for Ruby files. Only Ruby. The hook runs for me after every file
edit, holding Claude’s hand while I’m grabbing coffee. I might have it run
all “safe” cops in the future.
How Claude Code Hooks work
Hook Types
Five events trigger hooks:
- PreToolUse: Executes before tool calls. Can block actions.
- PostToolUse: Runs after tool calls complete.
- Notification: Triggers when Claude sends notifications.
- Stop: Executes when Claude finishes responding.
- SubagentStop: Runs when subagent tasks complete.
Hooks execute shell commands. They can match specific tools (e.g.
"WebFetch|WebSearch"
, "Edit|MultiEdit|Write"
) or apply globally (""
matches
all tool uses regardless of type). Common uses include automatic code formatting,
logging commands, sending notifications, and blocking sensitive file edits.
Configure hooks through the /hooks
command or in settings files. They require
careful security review since they run automatically during Claude’s workflow.
Other uses
Getting Claude to consistently lint, run tests etc. is a pain. I’ll probably
wrap that in a standard make
target and use that everywhere. For now, one
could do something like:
{
"hooks": {
"PostToolUse": [{
"matcher": "^(Edit|MultiEdit|Write)$",
"hooks": [{
"type": "command",
"command": "[ -f Makefile ] && grep -q '^lint:' Makefile && make lint || true"
}]
}]
}
}
Other ideas:
- Making sure certain tests always run
- Run
gofmt
or similar for other languages - Anything procedural and easily automated you have spelled out in CLAUDE.md
- For compiled languages, e.g. Java, golang, C++ just keeping your project compiling after every edit has sped me up considerably and produced more useful “vibe coded” outputs with less intervention and prompting from me.
- Blocking
git commit --no-verify
(ht to @jtdowney@infosec.exchange)
Safety Considerations
Hooks execute with full user permissions. Review all hook commands carefully—they can delete files, access networks, or run arbitrary code. Never copy hook configurations without understanding each command. Malicious hooks could compromise your system when Claude performs routine operations.