Make Claude Code run a typecheck before it says "done"

"Done" from an assistant is a claim, not a result. If the code does not typecheck, the claim is wrong, and you find out later. A Stop hook turns the claim into a check.

How a Stop hook works

Claude Code fires Stop when Claude is about to end its turn. Your script runs tsc --noEmit. If it fails, the script exits with code 2 and prints the errors. Claude Code then sends that text back to Claude, which fixes the problems before finishing.

Three details keep it safe and quiet:

// .claude/hooks/typecheck-on-stop.mjs (sketch)
import { readFileSync } from "node:fs";
import { spawnSync } from "node:child_process";

const input = JSON.parse(readFileSync(0, "utf8"));
if (input.stop_hook_active) process.exit(0);

const r = spawnSync("node_modules/.bin/tsc", ["--noEmit"], { encoding: "utf8", timeout: 120000 });
if (r.status === 0 || r.error) process.exit(0);

console.error(r.stdout.split("\n").slice(0, 25).join("\n"));
process.exit(2);

Use the project's local TypeScript from node_modules/.bin. That way the hook matches your own version rather than a global one.

When it feels like Claude will not stop

If Claude keeps working after it seemed finished, that is usually this hook sending type errors back. It only happens once per turn. If the check is too slow on a large project, you have two options: remove the Stop block from .claude/settings.json, or gate the script on an environment variable you set before starting claude, for example a SKIP_TYPECHECK=1 switch.

Pair it with a format hook

A second hook can run after each edit. It runs your project's own Prettier on the file Claude just changed, and does nothing at all when Prettier is not installed locally. This prevents formatting-only review noise and style drift.

One gotcha: if a file was never formatted, the first edit reformats the whole file. Format the codebase once in a separate commit to avoid noisy diffs.

Where this fits

A typecheck on stop catches type errors, not logic errors, and it does not replace tests, lint, a build, or review. For a fuller gate, a /preflight command that runs typecheck, lint, tests and build and prints one pass/fail table is a useful companion.

Remember the general rule: hooks are read at session start, so restart Claude Code after changing settings and confirm with /hooks.

Versions

A Stop hook like this depends only on the documented hook events and exit code 2. TypeScript 5.x and Prettier 3.x are the expected versions, and both are used only if present locally. Next.js itself does not matter to this hook, since it never imports it.

Going further

The Next.js Claude Code workflow pack includes a tested typecheck-on-stop hook, the Prettier hook and a /preflight command.

This article was written with AI assistance.