Agentic Development, Part 2: CLAUDE.md, Hooks, and Linters
Part 1 was the foundation: a repo, a host, a harness. Once you start actually shipping with an agent, you'll notice a pattern. The first session goes great. The second is fine. By the fifth, the agent is writing code that works but doesn't look like the rest of your codebase. Different naming, different patterns, the occasional rogue console.log.
That's drift, and it's the problem this post is about. The fix has two pieces I want to cover in depth here — the prompt-level rules you give the agent, and the hooks that fire on every tool call. Then Part 3 takes it the rest of the way with the tools those hooks should call.
CLAUDE.md: the agent's onboarding doc
Every Claude Code session starts by reading a CLAUDE.md file at the root of your repo. (Cursor reads .cursor/rules, Codex reads AGENTS.md — same idea, different filename.) Whatever you put in it becomes the agent's working context for the entire session. This is where you encode the things you'd tell a new hire on day one.
Run /init inside Claude Code and it'll generate a starter for you based on your project structure. Then edit it down. Hard.
The single most common mistake is making CLAUDE.md too long. The Claude docs themselves recommend keeping it under 300 lines, and shorter is better. Models bias toward the beginning and end of long prompts; the middle of a 1,500-line CLAUDE.md is the part the agent will quietly ignore. Treat every line like it's costing you instruction-following on something else, because it is.
What earns its keep:
- The map. Where things live. "Routes are in
app/. Components are incomponents/. Database schema is indb/schema.ts." This saves the agent (and you) from twenty grep calls per session. - Commands. The exact incantations to run tests, the dev server, the typechecker. "Run tests with
pnpm test. Run a single test withpnpm test path/to/file." Agents will guess wrong otherwise. - Hard rules. Things that are non-negotiable in this codebase. "Never commit without running
pnpm check." "All API routes must validate input with Zod." Keep these short and imperative. - Things they'd never guess. A weird build constraint, a deprecated module to avoid, the fact that your
utils/folder is haunted. Anything that bit you once and you don't want to explain twice.
What does not belong in CLAUDE.md: anything you can express as code. If you have a strong opinion about how to format a file, that's a job for the formatter, not for the prompt. If a pattern is forbidden, that's a job for the linter. The prompt is for things that can't be checked mechanically.
A real CLAUDE.md for a small Next.js project might be 60 lines. That's normal. Resist the urge to write a manifesto.
Hooks: the agent's spell-check
CLAUDE.md is guidance. The agent reads it, intends to follow it, and then sometimes forgets in the middle of a long session. Hooks are the mechanism that doesn't forget.
Claude Code hooks are shell commands that run automatically in response to events. The agent doesn't decide whether they run; you do, in .claude/settings.json. The most useful one is PostToolUse, which fires after the agent edits or writes a file.
The canonical first hook is "format every file the agent touches":
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx biome format --write 2>/dev/null; true"
}
]
}
]
}
}Every time the agent edits or writes a file, the hook receives the tool invocation as JSON on stdin, pulls the file path out with jq, and runs the formatter on it. The agent never sees unformatted code in its own output, which means it doesn't drift toward writing unformatted code in the next file.
The other event hooks worth knowing about:
PreToolUseruns before a tool call. You can block it by exiting non-zero. Good for "never let the agent runrm -rf" kinds of rules.UserPromptSubmitruns when you send a message. Useful for injecting fresh context — like the current branch name or a snippet of recent CI output.SessionStartruns once at the beginning. Good for setup tasks the agent shouldn't have to do itself.
Start with one PostToolUse hook running your formatter. Add more only when you have a specific drift problem to solve. Hooks that aren't earning their keep slow down every tool call, and the agent will notice the latency before you do.
What the hooks should call
A hook is only as good as the tool it invokes. The whole approach falls apart if your formatter takes four seconds, because that latency multiplies across every file the agent writes. You want sub-second feedback or it's not worth wiring up.
The short list of what to install on day one:
- Biome — single Rust binary that does what Prettier and ESLint used to do together, for JavaScript and TypeScript.
npx biome initto set up,biome check --write .to format and lint in one pass. - Ruff — single Rust binary that replaces Black, isort, Flake8, and a stack of other Python tools.
ruff check --fix .andruff format .. - Lefthook — Go-native git hooks manager. Wire your formatter and linter into
pre-commitso nothing slips through even if the Claude Code hooks misfire.
All three are fast enough that you'll happily run them on every file write. That speed is the whole game. Part 3 is the deep dive on each of these — config examples, why they beat the older tooling, and the more advanced layer that lives on top: AST rules for your custom conventions, dead-code analysis, and running multiple agents in parallel without them stepping on each other.
The pieces, so far
After Part 1 you had a place to push code, a place to deploy it, and an agent you could drive. After this post you should have:
- A short, sharp
CLAUDE.mdthat tells the agent how this codebase works. - At least one Claude Code
PostToolUsehook formatting files on every write. - A formatter and linter installed (Biome and/or Ruff) so the hook has something to call.
That's the conventions layer. It catches most of the drift the agent will introduce in a single session. The harder problem — the things linters can't catch, the patterns specific to your codebase, the dead code agents leave behind — is what Part 3 is for.