Two hooks worth stealing
Two hooks worth stealing. Both are PostToolUse hooks that close known weak points in how Claude Code handles larger projects — one for type safety, one for code duplication — and both show the self-correcting feedback loop in action. The goal here is not to narrate the click-by-click, but to give you the problem, the solution, the process, and the trade-offs, so you can adapt the pattern to your own codebase.
Hook 1 — The TypeScript type checker hook
The problem. You update a function signature and Claude edits the definition cleanly. But it misses the call sites. On a larger project, the call sites live across multiple files that Claude never opened, and the first you hear about it is when tsc blows up the next time someone builds.
This happens more often than you would like. It is not that Claude does not understand type errors — give it the error, and it fixes them immediately. The gap is that nothing prompts Claude to check.
The solution. A PostToolUse hook on Write|Edit|MultiEdit that runs tsc --noEmit after any TypeScript file edit. If the type checker finds errors, the hook writes them to stderr and exits in a way that feeds them back to Claude.
The process.
- Claude edits
schema.ts, updating thecreateSchemasignature to require a newverbose: booleanargument. - The PostToolUse hook fires and runs
tsc --noEmit. - TypeScript reports:
main.ts:42 — Argument for 'verbose' was not provided. - The hook writes that error to stderr.
- Claude sees the feedback on the next turn, opens
main.ts, and updates the call site. - The hook runs again, sees no errors, and everything is green.
The loop runs without you. You asked for a signature change once; Claude did the signature change, caught its own mistake, and fixed it.
The trade-off. tsc --noEmit takes a second or two, and it runs on every TypeScript edit. Usually worth it. On very large monorepos where a full type check is slow, you might scope the hook to specific directories or use tsc with incremental build info.
Why this pattern generalises. Any typed language with a type checker can do the same thing — Rust, Go, Kotlin, Swift. Untyped languages can use test runs instead: a PostToolUse hook that runs a fast test suite after edits and feeds failures back. The specific tool is less important than the pattern: after-edit check → feedback on stderr → Claude self-corrects.
Hook 2 — The query deduplication hook
This one is more nuanced, and it previews the SDK you will meet in the next lesson.
The problem. Claude sometimes creates new queries or helper functions instead of reusing ones that already exist. It is not random — it happens much more on wrapped tasks than on focused ones.
A focused task looks like: "Add a query that returns pending orders." Claude opens the queries directory, looks around, finds that no such query exists, writes it, moves on. Works fine.
A wrapped task looks like: "Build a Slack integration that notifies the team about pending orders." Claude's attention is on the Slack wiring. Somewhere in the middle of that, it realises it needs pending orders, and instead of pausing to check whether a getPendingOrders query already exists, it writes a new one inline. You end up with duplicates scattered across the codebase.
The solution. A PostToolUse hook that watches edits to the queries/ directory. When a query file changes, the hook spins up a separate Claude Code instance via the TypeScript SDK with a scoped prompt: "Review this diff for duplicates against the rest of the queries directory. If you find one, report it."
If the reviewer instance finds a duplicate, the hook exits with code 2, writes the finding to stderr, and the main Claude — the one doing the Slack integration — gets feedback like "a duplicate of this query already exists at queries/orders.ts:42, use the existing one." It deletes the new query, reuses the existing one, and carries on.
The process.
- Main Claude edits a file in
queries/as part of the Slack feature. - The PostToolUse hook fires.
- The hook launches a separate Claude Code instance via the SDK with a focused review prompt.
- The reviewer instance inspects the diff, scans the rest of the
queries/directory, and reports any duplicate it finds. - If a duplicate exists: hook exits 2, main Claude sees the feedback, removes the duplicate, reuses the existing query.
The trade-off. This hook costs real money and real time. Spinning up a second Claude instance on every edit to watched directories is not free. The recommendation is to only watch directories where duplication is genuinely painful — queries, schemas, shared utilities — not the entire codebase.
The pattern behind both hooks
Both hooks do the same fundamental thing: they catch a common Claude Code weakness automatically, provide structured feedback, and let Claude self-correct. The type checker hook is cheap and universally applicable. The deduplication hook is expensive but solves a problem that is otherwise hard to catch. You will build hooks on both ends of that spectrum, and the framing above — problem, solution, process, trade-offs — is the shape to use when you do.
Key Takeaways
- 1A PostToolUse hook that runs `tsc --noEmit` after TypeScript edits catches missed call sites and lets Claude self-correct without human intervention.
- 2The same after-edit-check pattern generalises to any typed language with a type checker, or to fast test suites for untyped languages.
- 3Focused tasks rarely produce duplicates; wrapped tasks (where the query is incidental to a larger goal) are where Claude loses track and rewrites existing code.
- 4The query deduplication hook uses the Claude Code SDK to spawn a second Claude instance as a reviewer, then feeds duplicate findings back via stderr.
- 5Hooks that use additional Claude instances cost money and time — scope them to directories where duplication actually matters.