Claude Code best practices from a year of daily use
Not a feature tour. These are the practices that survived a year of running Claude Code every day and building a product on top of its hook system: instruction files that stay small, permission rules that scale, hooks as gates, multi-session discipline, and verifying everything the agent claims.
Most "Claude Code best practices" posts are a feature list with adjectives. This one is a survivor list: I run Claude Code every day, ship a product whose core integration is its hook system, and have deleted more workflow advice than I currently follow. What is left below is only what kept earning its place. Where a topic deserves its own deep dive, I link the one we already wrote.
1. Keep CLAUDE.md small, and only put non-derivable things in it
CLAUDE.md is loaded into every session, so every line you add is a tax on every conversation. The failure mode is treating it as documentation. It is not documentation, it is standing orders, and standing orders bloat until nobody follows them.
What earns a line: things the agent cannot derive from the code. Build and test commands with their gotchas, the deploy rule ("never deploy without explicit approval"), which binary is the dev instance versus the real one, workflow rules like "confirm the plan before implementing". What does not earn a line: architecture descriptions the agent can read from the source, style rules a formatter already enforces, and history.
One structural trick that pays off: write down the why next to any rule that looks arbitrary. "Test against the dev bundle id" gets ignored; "debug builds used to clobber the real app's database, so always use the dev bundle id" gets followed. Agents, like new teammates, comply better with rules that carry their reason.
2. Scope permissions instead of turning them off
The permission prompt is annoying, so the popular fix is skipping permissions entirely. That works right up until the agent runs a destructive command in the wrong directory, and the whole point of an agent is that you are not watching every command.
The sustainable middle: keep the permission system on and grow an allowlist in settings.json as real prompts come in. Allow the specific commands your project actually uses (npm test, cargo build, your deploy dry-run) and let everything else keep prompting. After a week the prompts mostly stop, and the ones that remain are exactly the ones you want to see. Blanket-skipping trades a week of small annoyances for one very bad afternoon.
The corollary nobody mentions: once prompts are rare, an agent sitting at a permission prompt becomes easy to miss, and it will wait silently forever. That is a notification problem, covered in practice 5.
3. Plan first, then implement, as two separate steps
The single highest-leverage habit: do not let the first response be code. Ask for the plan, read it, correct it, then say go. On anything non-trivial, the plan step catches wrong assumptions when they cost one sentence to fix instead of a diff to unwind. Claude Code has a plan mode for exactly this, but the habit matters more than the feature; even a plain "tell me your plan first, do not edit anything yet" changes the shape of the session.
The same idea scales up: for features, write the spec into a file (specs/, or wherever), have the agent implement against the file, and keep the file updated when reality drifts. A spec the agent can re-read beats a decision buried 200 messages up in a conversation that will eventually be compacted away.
4. Use hooks as gates, not just as logs
Hooks are the most underused part of Claude Code. Most setups that use them at all use them for notifications, but the hook contract also lets you decide: a PreToolUse hook can block a command before it runs, and a Stop hook can refuse to let the turn end until tests pass. That turns "please always run the tests" from a request the model might forget into a rule it cannot skip.
Start with two gates: block obviously destructive commands (force-push, recursive delete outside the repo), and reject turn-end while the build is red. We published six hook configs worth stealing with the exact JSON, and a reference for what Claude Code actually passes to your hooks, because the payloads are underdocumented and half the difficulty is knowing what is in the stdin JSON.
5. Set up finish and blocked notifications before you think you need them
An agent turn is minutes long, so you will tab away; everyone does, and pretending otherwise is not a best practice. The problem is asymmetric: when the agent finishes, you find out late; when the agent is blocked on a permission prompt, you may not find out at all, because a blocked session looks exactly like a working one.
Claude Code fires distinct events for both cases, so wire both: a Stop hook for "turn ended" and a Notification hook for "waiting on you". The five-minute version with terminal-notifier is in how to get notified when Claude Code finishes. If your agent runs on a remote box, hooks fire on that box, not your laptop, and that needs a reverse tunnel.
6. Run parallel sessions, but make the sessions legible
One session per project, several projects at once, is where agent work actually pays off. Two rules keep it from collapsing:
First, isolate the work. Parallel sessions in the same working tree will trample each other's changes; git worktrees give each session its own checkout of the same repo, and are the difference between parallel and interleaved-badly.
Second, make "which session needs me" answerable in one glance. This is the multi-session version of practice 5: per-project notifications with the project name in them, and something that takes you to the right window. If your sessions live on a server, run them inside tmux so a dropped SSH connection does not kill a half-finished turn; the exact setup is four commands, plus the traps that only show up inside tmux.
7. Verify claims, keep context short, commit small
Three habits, one theme: the agent's summary of what happened is not evidence.
- Check exit codes and run the thing. "Tests pass" means you saw the test runner exit 0, not that the agent said so. For anything user-facing, run the happy path and one unhappy path yourself, or make a hook gate do it (practice 4).
- Keep sessions on one task. Long meandering context makes output worse and compaction lossier. Finish the task, commit, start fresh.
/compactis a tool, not a lifestyle. - Commit at every working checkpoint. Small commits are what make the agent's mistakes cheap:
git checkout .on a focused diff is a shrug, on a day of uncommitted work it is a disaster. The agent can write the commit messages; you should own when a checkpoint exists.
8. Decide what your waits are for
Adopt everything above and you have replaced typing time with waiting time: minutes-long turns, several times an hour, all day. That time defaults to Twitter, or to hovering over the terminal reading tokens as they stream. Neither survives contact with a week of real work.
The practices that stick treat waits as real time with a real owner. Queue the next task in another session (practice 6). Review the last diff. Or use it for something deliberately: Unwait exists because we wanted the wait to amount to something, so it shows a flashcard during the wait and gets out of the way the moment your agent needs you. Whatever you pick, pick it on purpose; the default is losing twenty minutes an hour to nothing.
The list, compressed
- CLAUDE.md is standing orders, not documentation; keep it small and give rules their reasons.
- Grow a permission allowlist; do not blanket-skip.
- Plan as a separate step before any code. Specs in files, not in chat scrollback.
- Hooks can block and gate, not just notify. Use that.
- Wire "finished" and "blocked" notifications; blocked is the expensive one.
- Parallel sessions with worktrees, tmux on remote, and per-session signals.
- Trust exit codes, not summaries. Short sessions. Small commits.
- Give the wait between prompts a job.
None of this requires more model or more spend; it is all workflow. The agents are already good enough that the difference between a great week and a frustrating one is mostly whether the human side of the loop is set up on purpose.