I was slow to adopt AI coding tools. My early experiments went the way a lot of people's did: impressive demos, then confidently wrong code in a real codebase, then an afternoon spent reviewing changes I could have written myself in the time.
What changed my mind wasn't a better model, although the models did get better. It was realising that Claude Code is a tool you configure, not a chatbot you prompt. Most of the value comes from giving it the right context, the right guardrails, and a workflow where I stay in charge of the decisions. This post covers the setup and habits that made it work for me.
CLAUDE.md: write down what you'd otherwise repeat
Claude Code starts every session with a fresh context window. The main way to carry knowledge between sessions is a CLAUDE.md file in your project root, which it reads at the start of each session.
Running /init generates a first draft by analysing your codebase. I treat that draft as a starting point and edit it heavily, because the valuable parts are the things Claude can't work out by reading code:
# CLAUDE.md
## Commands
- `make test` runs unit tests; `make test-integration` needs Docker running
- `make lint` must pass before any commit
## Conventions
- Errors: wrap with fmt.Errorf("doing x: %w", err); never log and return
- New HTTP handlers go in internal/httpapi and must not contain business logic
- Use the existing internal/clock package instead of calling time.Now() directly
## Gotchas
- The payments sandbox rejects amounts under 50 cents with a generic 500
- Migrations are append-only; never edit a migration that has been merged
Some things I learned about writing it:
- Be concrete enough to check. "Run
make lintbefore committing" works. "Write clean code" does nothing. - Keep it short. The docs suggest staying under about 200 lines per file, because longer files use more context and get followed less reliably. Details that only matter for one area can go in scoped rule files under
.claude/rules/. - Add to it when you correct the same mistake twice. If I've explained something to Claude twice, it belongs in
CLAUDE.md.
There's also a user-level ~/.claude/CLAUDE.md for personal preferences across all projects, and a CLAUDE.local.md for project-specific notes you keep out of git.
Plan mode for anything non-trivial
This is the habit that made the biggest difference. For any change beyond a small fix, I start in plan mode. Press Shift+Tab until the status bar shows plan mode, or start with claude --permission-mode plan. In plan mode, Claude reads the code and proposes a plan, but edits nothing until I approve.
Why it matters: most of the time AI-generated code goes wrong, the code itself is fine, but it's solving the wrong problem or solving it in the wrong place. Reading a ten-line plan takes a minute. Reviewing a 400-line diff built on a wrong assumption takes much longer, and throwing it away hurts.
A good plan names the files it'll touch, the existing helpers it'll reuse, and how it'll verify the change. If a plan invents a new utility when one already exists, I catch it at the plan stage rather than in review.
Keep context clean with subagents
The context window is a limited resource. Every file Claude reads to answer "how does auth work here?" takes up space that's then unavailable for the actual work.
Subagents solve this. A subagent runs in its own separate context window, does its research, and sends back only a summary. Claude Code ships with built-in ones such as Explore, a fast read-only agent for searching a codebase. Often I just ask:
use a subagent to find every place we construct an HTTP client without a timeout
You can define your own as Markdown files with YAML frontmatter in .claude/agents/:
---
name: migration-reviewer
description: Reviews SQL migrations for locking and backward-compatibility problems. Use when a migration file changes.
tools: Read, Grep, Glob
---
You review PostgreSQL migrations. Flag anything that takes a long lock on a
large table, drops or renames a column still used by the running code, or
adds a NOT NULL column without a default. Report findings as a list with
file and line.
Limiting the tools to read-only ones means a reviewer agent can't "helpfully" change the thing it's reviewing.
Hooks: rules that aren't optional
CLAUDE.md is guidance. Claude reads it and tries to follow it, but nothing forces it to. When something must happen every time, I use hooks: shell commands that Claude Code runs at fixed points in its lifecycle, whatever the model decides.
My most common one formats every Go file right after Claude edits it. In .claude/settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | grep '\\.go$' | xargs -r gofmt -w"
}
]
}
]
}
}
The hook receives the tool call as JSON on standard input. This one pulls out the file path, keeps it only if it's a Go file, and runs gofmt on it. Formatting is never something I need to remind Claude about again.
PreToolUse hooks can block actions: if the hook exits with code 2, the tool call is stopped and the hook's stderr message is shown to Claude. I use one to protect files that should never be edited by an agent:
#!/usr/bin/env bash
# .claude/hooks/protect-files.sh
path=$(jq -r '.tool_input.file_path // empty')
case "$path" in
*.env|*/migrations/*)
echo "Editing $path is not allowed; ask the user to do it." >&2
exit 2
;;
esac
exit 0
It's registered the same way as the formatter, under PreToolUse with the matcher Edit|Write, and with the command pointing at the script ("$CLAUDE_PROJECT_DIR"/.claude/hooks/protect-files.sh).
Skills for repeatable workflows
When I catch myself pasting the same multi-step instructions again, such as "run the integration tests, check the logs for a panic, summarise the failures", it becomes a skill. A skill is a SKILL.md file in .claude/skills/<name>/. Its YAML frontmatter has a description that tells Claude when to use it, and the Markdown body contains the instructions.
Skills can be invoked directly as /name, and Claude can also load them on its own when the description matches the task. The older custom slash commands in .claude/commands/ still work. They've been merged into the same system.
The big advantage over putting everything in CLAUDE.md: a skill's body only loads when it's used, so a long checklist costs almost nothing until you need it.
MCP for context outside the repo
The Model Context Protocol lets Claude Code talk to external systems: issue trackers, databases, documentation, internal tools. I connect only what I actually use in a task, like the issue tracker so Claude can read the ticket it's working on. Every server you connect adds tools that compete for Claude's attention and context, so more isn't better.
Habits that matter more than any feature
Always ask for verification. My prompts nearly always end with how to prove the change works: "run make test and the affected integration test, and show me the output." A change Claude has run is worth far more than one it only wrote.
Keep sessions focused. One task per session. Use /clear when switching topics so leftover context from the last task doesn't bleed into the next. When a long session gets heavy, /compact summarises the history to free up space.
Parallelise with worktrees. claude --worktree <name> starts a session in its own git worktree, so two tasks can move forward in parallel without editing the same files.
Review like it's a colleague's PR. Because it is. I read every diff, question anything surprising, and ask "why this approach?" when I'm not sure. The accountability for what ships is still mine.
What it's good at, and where I stay careful
In my experience, Claude Code does well at work that's well-specified but tedious: adding tests for existing behaviour, mechanical refactors across many files, following an established pattern into a new module, tracking a bug through unfamiliar code, and writing the first draft of documentation.
I stay much more careful with anything security-sensitive, with performance work where intuition is easy and measurement is hard, and with design decisions that shape the codebase for years. Claude can be an excellent sparring partner there. It'll happily list trade-offs and poke holes in my reasoning. But those decisions stay with me.
Getting started
If you're new to it, don't try to set everything up at once:
- Run
/init, then editCLAUDE.mddown to what really matters. - Use plan mode for your next non-trivial change.
- Add a formatting hook.
- Turn the next set of instructions you paste twice into a skill.
The official documentation covers all of this in depth and changes often, so check it for the details. The tool rewards investment: the more of your team's knowledge you write down for it, the more it behaves like a colleague who's read the whole codebase.
Using Claude Code on a team? I'd love to compare notes. Drop me a message on the contact page.