# Configuration Best Practices

> Concrete, actionable patterns. No generic advice.

---

## CLAUDE.md

1. **Optimise for prompt-cache stability.** Place stable content in the first 30 lines (cache-friendly prefix); volatile content (timestamps, dynamic counts, rolling activity logs) goes below that threshold or moves to an `@import`-ed file outside the cache prefix. On Opus 4.7 the dominant cost lever is cache reuse, not file length.[^200lines]
2. **Use `@import` for specs/docs.** `@path/to/spec.md` inlines the file at session start. Max 5 hops, but keep chains ≤ 2 hops — every `@import` boundary fragments the prompt-cache prefix. Keeps the main file scannable.
3. **Use HTML comments for maintainer notes.** `<!-- Updated 2026-01-01: reason -->` is stripped before context injection — zero token cost.
4. **Put personal dev notes in `CLAUDE.local.md`**, not `CLAUDE.md`. Add `CLAUDE.local.md` to `.gitignore`. Team members' sandbox URLs should never appear in git.
5. **Write `~/.claude/CLAUDE.md` for preferences that apply everywhere.** Communication style, preferred tools, output format — not project-specific config.
6. **Use clear markdown headers** (`##` sections). Claude uses the structure to navigate; unstructured text is harder to follow selectively.
7. **Avoid contradicting project settings.json.** CLAUDE.md is a user message; settings.json permissions take precedence. Don't document permissions in CLAUDE.md — put them in settings.json where they're enforced.

---

## settings.json

1. **Add `$schema` to every settings.json.** `"$schema": "https://json.schemastore.org/claude-code-settings.json"` enables autocomplete in VS Code and Cursor. Takes 2 seconds, saves every future edit.
2. **Use all three scopes: user, project, local.** User (`~/.claude/settings.json`) for personal defaults. Project (`.claude/settings.json`) for team agreements. Local (`.claude/settings.local.json`) for personal project overrides.
3. **Put env vars in `settings.json` `env` block, not shell.** `{"env": {"NODE_ENV": "development"}}` ensures they're always set in Claude sessions, regardless of how the shell was launched.
4. **Set `defaultMode: "acceptEdits"` for active development projects.** Eliminates per-file permission prompts. Use `"plan"` for infrastructure repos where you want read-only analysis by default.
5. **Deny `.env` and `secrets/` explicitly.** `{"permissions": {"deny": ["Read(./.env)", "Read(./secrets/**)"]}}` — Claude cannot read these even if it reasons it should.
6. **Pre-allow repetitive safe commands.** `{"permissions": {"allow": ["Bash(npm run *)", "Bash(git status)", "Bash(git log *)"]}}` — eliminates constant prompts for read-only git operations.
7. **Configure `attribution` for org identity.** `{"attribution": {"commit": "Generated with Claude Code [bot]", "pr": ""}}` — keeps commit history clean and attributable.
8. **Set `effortLevel` per project, not per prompt.** `{"effortLevel": "high"}` for complex codebases, `"low"` for simple scripts. Avoids forgetting to set it each session.

---

## Hooks

1. **Add a `Stop` hook before anything else.** `Stop` hook on session end is the most useful starting point — session summary, auto-commit prompt, notification. Many users have zero hooks; one Stop hook delivers immediate value.
2. **Use `PostToolUse` on Write/Edit for auto-formatting.** `{"PostToolUse": [{"matcher": "Write|Edit", "hooks": [{"type": "command", "command": "prettier --write ${CLAUDE_TOOL_OUTPUT_PATH}"}]}]}` — eliminates manual format steps.
3. **Use `PreToolUse` on Bash for security.** Validate shell commands before execution. Exit code 2 blocks the tool call with an error message shown to Claude.
4. **Use `SessionStart` for context injection.** Inject git branch name, active Linear issue, or CI status into context at session start. Cheaper than asking Claude to fetch it.
5. **Add `Notification` hook for desktop alerts.** When Claude needs input (permission prompt, idle), get a system notification. Without this, long sessions require constant manual checking.
6. **Match MCP tools precisely.** `"mcp__.*__write.*"` matches all write tools from all MCP servers. `"mcp__filesystem__.*"` matches all filesystem tools. Use patterns, not exact names.
7. **Keep hook scripts fast (< 2s for PreToolUse).** Blocking hooks run synchronously. Slow PreToolUse hooks add latency to every tool call. Use async for logging/reporting.
8. **Use `${CLAUDE_PLUGIN_ROOT}` for paths in plugin hooks.** Absolute paths break when plugins move. `${CLAUDE_PLUGIN_ROOT}/hooks/scripts/check.sh` is portable.

---

## Rules (.claude/rules/)

1. **Use `paths:` frontmatter on every rules file.** Rules without `paths:` load for every file. A TypeScript rules file with `paths: ["**/*.ts", "**/*.tsx"]` only loads for TypeScript work — zero overhead otherwise.
2. **One rules file per domain or language.** `typescript.md`, `python.md`, `testing.md`, `migrations.md` — not one big `coding-rules.md`. Granular files = granular loading.
3. **Put project rules in `.claude/rules/`, user rules in `~/.claude/rules/`.** Project rules are team-specific and committed; user rules are personal preferences across all projects.
4. **Symlink shared rule sets.** If multiple projects share rules, symlink: `ln -s ../../shared/rules/security.md .claude/rules/security.md`. Claude follows symlinks.
5. **Test path globs before committing.** `paths: ["src/**"]` doesn't match `./src/file.ts` — leading `./` matters. Test with the actual file paths Claude will encounter.

---

## MCP

1. **Commit `.mcp.json` to git.** Team-shared MCP servers belong in `.mcp.json` at project root, not in individual `~/.claude.json` files. One commit, everyone gets the servers.
2. **Set `enableAllProjectMcpServers: true` in project settings.json** for zero-friction team onboarding. New team members don't have to manually approve each server.
3. **Set trust levels explicitly.** `"trust": "workspace"` for project-specific servers; `"trust": "trusted"` only for servers you fully control. Default is untrusted (sandboxed).
4. **Use `@server:resource/path` for dynamic data.** `@github:repos/owner/repo/issues` pulls live data into context. More reliable than asking Claude to fetch and parse.
5. **Deny MCP tools you don't want Claude to invoke.** `{"permissions": {"deny": ["mcp__filesystem__write_file"]}}` — even with a server connected, specific tools can be blocked.

---

## Skills

1. **Add `description` to every skill.** Without `description`, Claude never auto-invokes the skill. The description is the trigger — be specific about when to use it.
2. **Set `disable-model-invocation: true` on deploy/delete skills.** Side-effect commands should only run when the user explicitly types `/deploy`, not when Claude decides it's appropriate.
3. **Use `!`git diff HEAD`` for dynamic context.** Dynamic shell execution inlines current state at invocation time. Better than hardcoded file references that go stale.
4. **Use `context: fork` with a custom agent for isolated research.** Forks run in a separate context (and optionally a separate model), keeping research overhead out of the main session.
5. **Add `argument-hint` to all parameterized skills.** `argument-hint: "[issue-number]"` shows in the `/` menu autocomplete. Without it, users forget the expected argument format.
6. **Store large reference docs in skill subdirectory, not SKILL.md.** SKILL.md describes *when to load* each reference file. The references themselves stay separate so they're only loaded when needed.

---

## Agents

1. **Restrict tools to the minimum needed.** A read-only research agent should have `tools: ["Read", "Glob", "Grep"]`, not all tools. Scoped agents are safer and faster.
2. **Match model to task complexity.** Haiku for file discovery and scanning; Sonnet for implementation; Opus for architecture and analysis. Don't use Opus for tasks that are primarily file reading.
3. **Set `maxTurns` on autonomous agents.** Without a turn limit, a misconfigured agent can run indefinitely. `maxTurns: 20` is a reasonable default for most tasks.
4. **Write `description` as a trigger condition, not a title.** "Use when analyzing TypeScript files for type errors" beats "TypeScript analyzer". Claude uses the description to decide delegation.
5. **Use `isolation: worktree` for agents that make file changes.** Agents running in their own worktree can't interfere with the main session. Changes are reviewable before merge.
6. **Enable `memory: "user"` for domain-expert agents.** A security-reviewer agent that accumulates codebase knowledge across sessions gets better over time. Add `memory: "user"` to the frontmatter.

---

## Permissions

1. **Start with `defaultMode: "acceptEdits"`** for most projects. Then add specific `deny` rules for sensitive paths. More productive than prompting for every file write.
2. **Block secrets files by pattern, not by name.** `"deny": ["Read(./.env*)", "Read(./**/secrets/**)", "Read(./**/*.pem)"]` — catch all variants, not just `.env`.
3. **Use `additionalDirectories` for cross-repo work.** If Claude regularly reads `../shared-lib/`, add it: `{"additionalDirectories": ["../shared-lib/"]}`. Otherwise Claude can't access it without prompts.
4. **Configure `autoMode.environment` before using auto mode.** Without it, Claude's background safety classifier triggers false positives on your org's internal tool names and domains.
5. **Add `Agent()` deny rules for sensitive agents.** `{"deny": ["Agent(general-purpose)"]}` prevents the most powerful agent from running without explicit permission.

---

[^200lines]: The "keep CLAUDE.md under 200 lines" threshold was a Sonnet-era adherence heuristic — Sonnet's attention quality dropped on longer files, so trimming raw line count was the optimisation lever. Opus 4.7 uses prompt-cache structure as the dominant cost driver: the first 30 lines must stay byte-stable across turns to keep the cache hit, and `@import` boundaries fragment the cached prefix. A 400-line CLAUDE.md with stable structure outperforms a 150-line file whose top contains a daily-rolling activity log. See `knowledge/opus-4.7-patterns.md` for detection IDs (CA-TOK-001..003).
