How to Set Up Lifecycle Hooks for Automated Enforcement in Claude Code
Problem
When I wrote rules in CLAUDE.md like “always run typecheck before committing,” Claude sometimes forgot to follow them. I kept adding more rules, but compliance didn’t improve.
The core issue: rules are text that requests behavior. Claude processes them as suggestions, not guarantees.
Rule in CLAUDE.md: "Always run typecheck before committing"Claude's behavior: Sometimes remembers, sometimes forgetsResult: Inconsistent code qualityEnvironment
- Claude Code (latest)
- TypeScript project
- CLAUDE.md with 150+ lines of rules
What happened?
I noticed that rules at line 137 of a 200-line CLAUDE.md were often ignored. When I investigated, I found:
- LLMs process rules as suggestions, not guarantees- Long CLAUDE.md files push older rules out of context- The model may "decide" the rule doesn't applyOne Reddit user put it perfectly: “One of the statistically possible results of saying ‘don’t commit without linting first’ is for the tool to decide to commit without linting first.”
How to solve it?
I replaced rules with lifecycle hooks.
The Hook Types
Claude Code supports three hook types:
| Hook Type | When It Runs | Use Cases |
|---|---|---|
| PreToolUse | Before tool execution | Validation, blocking dangerous actions |
| PostToolUse | After tool execution | Auto-formatting, type checking, linting |
| Stop | When session ends | Final verification, audit logging |
Solution #1: PostToolUse Typechecking Hook
I added a hook that runs TypeScript type check after every edit:
{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx tsc --noEmit" } ] } ] }}Now Claude doesn’t choose to be typechecked. The environment enforces it.
Solution #2: PreToolUse Git Push Review
I added a hook that opens a diff review before git push:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "echo 'Review changes before push' && code --wait --diff" } ] } ] }}Solution #3: Comprehensive Hook Configuration
Here’s my full hook setup:
{ "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "tmux-reminder.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "prettier --write $FILE_PATH" } ] }, { "matcher": "Edit|Write", "glob": "*.ts", "hooks": [ { "type": "command", "command": "npx tsc --noEmit" } ] } ], "Stop": [ { "type": "command", "command": "audit-console-logs.sh" } ] }}The reason
I think the key reason hooks work better than rules is:
- Deterministic enforcement: Hooks run every time, regardless of context
- No memory required: The hook doesn’t need Claude to remember it exists
- Infrastructure vs documentation: Rules are text hoping for compliance; hooks are code guaranteeing compliance
After implementing hooks, my review time dropped dramatically. By the time I looked at the code, the structural problems were already gone.
Summary
In this post, I showed how to set up lifecycle hooks in Claude Code for automated enforcement. The key point is: rules degrade, hooks don’t. Instead of hoping Claude remembers your rules, build infrastructure that enforces them automatically.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments