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:

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":

json
{
  "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:

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:

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:

  1. A short, sharp CLAUDE.md that tells the agent how this codebase works.
  2. At least one Claude Code PostToolUse hook formatting files on every write.
  3. 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.