← guides · 2026-07-27

Claude Code best practices, from a year of daily use

How I've configured Claude Code as an operating layer rather than a chat window: adaptive MCP profiles, three-tier subagent routing, verification rules that stop the model grading its own homework, hooks that enforce instead of remind, and why I turned the reasoning effort dial back down.

Most Claude Code advice is about prompts. Almost none of it is about the thing that actually decides whether a session goes well: what is in the context window, and what it cost to put there. Every connected MCP server loads its tool definitions every turn. Every enabled plugin loads its skills and commands the same way. A full stack of 25 MCP servers and a dozen plugins is a permanent tax, and in any given directory most of it is dead weight.

This is what I run after about a year of using Claude Code as the interface to email, calendar, research, writing, automation, and code. The specifics are mine; the mechanics transfer. The code behind most of it is in maven-template and the cross-model agent delegation kit.

Context is the scarce resource

Everything below is one idea applied in different places. Model quality is rarely the bottleneck. What breaks a long session is a context window filled with tool schemas you never call, search results you read once, and file contents that mattered for two turns.

Four levers, in order of how much they buy you:

  1. Load fewer tools. Per-directory MCP and plugin profiles.
  2. Load knowledge on demand. Skills instead of always-on rules.
  3. Let something else do the reading. Subagents and CLI offloading return summaries, not raw material.
  4. Compact later. CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=75 triggers compaction at 75% so you keep headroom before the summary lands.

Where each kind of knowledge belongs

Four places, and putting something in the wrong one is the most common configuration mistake.

CLAUDE.md is always in context, every turn, forever. It should hold only what must be true in every session: communication style, safety rules, the routing preferences that change default behavior. Keep it short enough to read in one sitting.

Rules files (.claude/rules/*.md) auto-load per project. Split project instructions across small focused files instead of growing one enormous CLAUDE.md. Easier to maintain, easier for the model to parse.

Skills load on demand. This is where domain knowledge goes: org context, brand guidelines, program details, procedures. The model reads only each skill’s name and description at startup, then pulls the body in when it decides the skill applies. That progressive loading is why a hundred installed skills cost almost nothing until one fires. My rule: if it’s reference material, it’s a skill. If it changes how the model behaves in every session, it’s a rule.

MCP servers are the expensive tier, because their tool schemas are unconditional. Which is the next section.

Adaptive MCP profiles

A SessionStart hook resolves a profile from the working directory, then toggles MCP servers (the disabled flag in ~/.claude.json) and plugins (enabledPlugins in settings.json) to match. Resolution order: an explicit directory-to-profile map, then auto-detection from file signatures (package.json plus tsconfig.json, pyproject.toml, next.config.*, Cargo.toml, go.mod), then a CLAUDE_PROFILE environment override.

A few profiles I run:

ProfileLoadsOff
codingfirecrawl, taskmaster, gemini, context7, playwright, TS/Py LSPcomms, content, n8n
contentparallel-search, jina, firecrawl, exa, perplexity, n8n-mcp, linkedin, apifycoding, comms
defaultparallel-search, geminieverything else
nonenothingall

The n8n MCP and its two skill plugins are only useful when building workflows, so they load in one profile and nowhere else. A coding session stops carrying Outlook, Asana, and LinkedIn schemas it will never call.

Two things to know. Changes apply on the next session, because MCPs and plugins load at startup. And language servers are real per-session overhead, so I keep TypeScript, Pyright, Kotlin, and Swift off unless the profile says otherwise.

The same pruning logic applies to plugins generally. Carrying a plugin you don’t use costs context every session. I keep about eleven enabled and the rest installed-but-off. One trap: official Anthropic marketplaces auto-update at startup, but GitHub-sourced marketplaces freeze at the version cached when you added them. That’s how a community plugin goes stale while everything around it stays current. Set "autoUpdate": true on its extraKnownMarketplaces entry, or run /plugin marketplace update <name> by hand.

Subagents are compression, not parallelism

The usual framing is that subagents make things faster. They do, sometimes. The reason to reach for one is that a task will generate intermediate noise you don’t want in the main context. A researcher runs five searches and returns 300 words. The orchestrator ingests 300 words instead of five result sets.

Spawn when the work produces noise: three or more web searches, three or more integration API calls, long content that needs research first, any genuinely independent subtask where only the summary matters.

Handle it directly when the task is small (one or two searches, a single API call), when you’ll need to make several decisions mid-flight, or when you’re revising something that already exists.

Pin the model, every time

Ad-hoc subagents default to model: inherit, which means an Opus session burns Opus on grep work unless something says otherwise. Custom agents in ~/.claude/agents/ should pin a tier in frontmatter; built-in agent types have no pin and need model passed at call time.

Three tiers beat two:

  • Haiku for mechanical retrieval and formatting. Task CRUD, short messages, dependency scans. Structured calls where nothing has to be worked out.
  • Sonnet as the default. Research, review, extraction, docs, test writing, bug tracing. It follows structured prompts (output formats, brand rules, tool preferences) as reliably as anything above it.
  • Opus only via a call-time override, for deep architectural judgment and long-form prose where quality actually changes the output.

Over dozens of dispatches a day, the haiku tier is real money on top of the sonnet-versus-opus split. Anthropic benchmarked the same shape on BrowseComp: a strong orchestrator with Sonnet workers hit 86.8% at $18.53 per problem against 90.8% at $40.56 for all-strong, and all-Sonnet managed 77.8%. Most of the accuracy, less than half the cost, and the split is what buys it.

Never split research from writing

The agent that gathers the information should produce the output. Handing research results to a separate writing agent is a telephone game: the second agent works from a summary of a summary and loses the specifics that made the research worth doing. A content agent researches and drafts in one context. A research agent gathers and synthesizes in one context.

Offload reading to another model

CLI models read files directly and return only a summary. That is a context transaction, not a quality judgment: when a CLI call reads fifteen files and hands back 200 words, your session ingests 200 words.

Route it deterministically rather than letting the model pick flags each time. My Codex calls go through a wrapper that takes a task class (commit, implement, explore, ingest, review, hardest) and pins model, reasoning effort, and sandbox from that class. The model classifies; the script owns every flag.

There’s a second reason to run a cross-family worker. Claude quota and ChatGPT quota are separate pools, so routing self-contained review and implementation work to a GPT worker makes your Claude limits last longer. You also get a real review bonus: a GPT model reviewing Claude’s work catches errors two same-family models share.

One gotcha worth the paragraph: -c 'mcp_servers={}' does not disable MCP servers on the Codex CLI. TOML table overrides merge, so an empty table is a no-op. The switch that works is --ignore-user-config, which also drops the plugin cold-start.

Hooks enforce; instructions remind

An instruction in CLAUDE.md is a preference the model usually follows. A hook is a thing that happens. Anything you actually need to be true belongs in a hook.

The ones earning their place here:

  • SessionStart loads MCP secrets from a .env, then runs profile detection. The secrets script unsets ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN before servers connect, because a stray key in .env silently bills the API instead of using your subscription.
  • PreToolUse on large reads blocks an unscoped read of any text file over 64 KB and returns three options: grep to locate and read a range, delegate to a cheap subagent for a summary, or read it in successive chunks. Choose by task. The range read is right when you’re editing; delegation only wins when a summary suffices and the file is big enough that its raw text would otherwise re-send every turn.
  • PreToolUse on clipboard writes runs a prose linter over anything headed for pbcopy and blocks the copy until it’s clean. Catching a style violation before the text leaves is worth more than catching it after.

Make it check its own work

If I could keep one thing from this setup it would not be any of the context machinery above. It would be this: give the model a way to observe the result, then require it to use that way. When Claude can close the feedback loop itself, it iterates until the output is right. When it can’t, it guesses and reports the guess with the same confidence it would report a verified fact.

The rule in my global config is four sentences:

Give yourself a way to observe the result, then use it. If a check exists (test suite, bash command, browser, simulator, log, live app), run it and report what you actually saw. A claim that something works is not evidence that it does. This applies to delegated work too: re-run the check yourself, and say which you did, observed or reported.

That last distinction does most of the work. A subagent saying “tests pass” and you having watched eight tests pass are different facts, and treating them as one is how a bad assumption survives three sessions. Making the model label which one it has costs a word and changes what you trust.

What counts as a way to observe depends on the surface: a browser for anything with a frontend, a test suite or a dry run for code, rendering the file for a document, re-querying the source for a number, a human pressing the button for anything involving hardware. The failure is not having no check available. It’s having one and not wiring it up.

Adversarial review, and the trick that makes it terminate

Self-review doesn’t work. A model that just wrote something defends its own choices, and asking the same context to double-check produces agreement dressed as scrutiny. What works is a fresh context with an explicitly skeptical framing, on a different model family where possible.

Two refinements matter more than the framing.

The first is attack the plan, not the diff. Reviewing the approach before anything is built catches architectural mistakes while they still cost nothing.

The second fixes the failure mode that makes people abandon adversarial review after a week. An agent told to “find the problems” will always find problems, including invented ones, and will never signal that it’s finished. You get an endless review cycle and you learn to ignore the output. The fix is to change the instruction:

State what must be true for this to be correct, then check those things.

Have the reviewer enumerate the conditions the work rests on (facts that must hold, people who must act, numbers that must land in a range) and mark each supported, unsupported, or unknown. That converts an open-ended hunt into a bounded checklist with a stopping criterion. A condition nobody can challenge is a green light, not a failure to find something.

Turn the effort dial down

I ran the highest reasoning effort setting for months on the theory that reasoning is free on a subscription. That was wrong twice over.

Subscription reasoning isn’t free, it’s rate-limited, and the weekly window is the real budget. More importantly, maximum effort makes ordinary work worse rather than merely slower. On current frontier models it over-plans bounded tasks, adds scope nobody asked for, and revises correct first answers into wrong ones. The behavior people describe as a model “going off the rails” is often just too much effort applied to something that didn’t need it.

Set a moderate default and raise it per task. Pin higher effort in the definitions of the specific agents that need depth, where it survives changes to the global default, so your adversarial reviewer stays sharp while a documentation grep stops thinking for thirty seconds.

The same logic applies to your instructions. Guardrails written to stop an older model’s failure modes read to a newer one as constraints to satisfy, and satisfying them costs turns. Audit periodically: open a fresh session, hand it your CLAUDE.md and your skills, and ask what’s unnecessary or redundant. Expect to cut a quarter of it. If a session feels like it’s flailing on something simple, suspect your config before you suspect the model.

Permissions: auto plus an allow-list

I ran bypassPermissions for a while. It’s the wrong default even when nothing goes wrong, because novel commands run silently alongside routine ones.

The replacement is defaultMode: "auto" with three lists. allow holds around 150 pre-approved patterns (git, gh, npm, the MCP tools I call constantly) and runs without a prompt. ask holds the destructive set: rm -rf, rm -r, git push --force, git reset --hard, git clean -f. deny hard-blocks chmod -R 777.

Day-to-day friction is the same as bypass, because the common patterns are covered. The difference is that anything genuinely new surfaces a prompt. The allow-list grows on its own: every “always allow” answer appends a pattern.

If you drive sessions from the phone, mirror ask and deny into remote-settings.json. Remote-initiated runs should inherit the same guardrails as the ones you start at the desk.

Separately, and regardless of mode: confirm before external actions. Sending mail, posting to a channel, modifying tickets, publishing. The cost of an errant message is asymmetric.

Watch the meter

On a subscription with weekly caps, the failure mode is burning the week’s budget by Thursday and not noticing until you hit the wall. A custom statusline turns that into a number you glance at:

Opus 5 | xhigh | 142k 14% | Session 23% · 3h12m | Week 61% · 4d6h

Two design notes. Compute the percentages from cost in USD, not raw tokens: cache-read tokens bill at roughly a tenth of fresh tokens, so token-based divisors drift badly as a conversation grows while cost stays stable. And project the burn rate to the end of the window rather than reporting the current total, so the gauge tells you whether to ease off now.

Calibrate by snapshotting your usage tool’s cost and the reported percentage at the same moment, then re-calibrate every few weeks. Caps move without announcement.

Persistence is the part people skip

State files, session logs, and auto-memory are the single largest productivity gain in this setup, and the least discussed. A short current.md with active priorities, open threads, and recently completed items means a new session opens with the model already knowing where things stand. No re-explaining.

For memory specifically, two rules keep it from rotting. Write down only what the repo doesn’t already record: code structure, past fixes, and git history are all better retrieved than remembered. And treat every memory as point-in-time. If a memory names a file, a flag, or a config value, verify it against the live system before acting on it.

If you’re starting today

Do these five, in this order, and skip everything else until they’re running.

  1. Write a short CLAUDE.md with your communication rules and your confirm-before-sending list. Resist making it long. There’s a fill-in-the-blanks starter if you want one.
  2. Add a verification rule and give the model something to verify against. A test command, a browser, a render step: whatever closes the loop for the work you do.
  3. Turn off every MCP server you haven’t called this week. Add them back per directory.
  4. Pin model: sonnet on every subagent definition you have, and only override upward when you can name why.
  5. Convert your most-repeated instruction into a hook. You’ll immediately see which of your other instructions were being ignored.

The whole discipline is one question asked repeatedly: is this in the context window because it’s needed right now, or because it was easier to leave it there? For the packaged versions of the skills and delegation patterns above, see /skills and /tools.