Claude Code Setup, Level by Level
Three levels of configuration, each with a concrete result. From a 10-line CLAUDE.md to an agent that remembers your project across sessions.
Prerequisites
- → Claude Code installed (claude.ai/code)
- → jq installed (required by the hooks below)
What you'll set up
- ✓ A minimal CLAUDE.md that eliminates repetitive context-setting from day one
- ✓ RTK installed for 60-90% token reduction on CLI commands
- ✓ Three hooks: credential leak blocker, auto-formatter on every file edit, and commit format validation
- ✓ Two skills that give Claude instant commit and session-restore capabilities
- ✓ A memory system that persists project knowledge between sessions
The first sign you haven’t configured Claude Code is usually around session three. You’re mid-task and Claude suggests npm install, even though your repo uses pnpm. Or it asks which testing framework you’re on, despite that being in your package.json. Or it generates a commit message that doesn’t match your team’s conventions, because you never told it what those were.
None of this is a Claude limitation. It’s a setup problem, and each level below fixes one layer of it.

Level 0 (Minute 1): CLAUDE.md
The minimum viable CLAUDE.md goes in the project root. Ten to fifteen lines, nothing more.
# My Project
Next.js 15 App Router, TypeScript strict, Prisma 5, pnpm 9.
## Commands
- `pnpm dev` - dev server (port 3000)
- `pnpm test` - unit tests (Vitest)
- `pnpm build` - production build
- `pnpm lint` - ESLint + type check
## Conventions
- Commits: feat/fix/docs(scope): subject
- No `any` types, no eslint-disable without an inline comment
- API routes versioned at /api/v1/
Three things that earn a line in this file. First: anything Claude cannot discover by reading the codebase, specifically your package manager, team conventions, non-obvious tooling choices. Second: something you’ve caught yourself repeating across sessions. Third: a convention that conflicts with common defaults, like using pnpm instead of npm, or having a non-standard test command.
What doesn’t earn a line: your file structure (Claude can read it), your dependencies (they’re in package.json), your TypeScript config (it’s in tsconfig.json). If Claude can discover it, don’t document it. A stale entry referencing something you deprecated six months ago biases Claude toward the wrong thing on every session.
Result: Claude answers in the right context from your first message, with no setup prompting on your end.
Level 1 (Day 1): RTK, three hooks, two skills
Four additions that take about 25 minutes total. After this level, Claude knows your project conventions and has repeatable shortcuts for the things you do every day.
RTK
RTK wraps CLI commands and filters their output before it reaches Claude’s context window. git log --oneline -50 generates roughly 800 tokens by default. rtk git log generates around 80. The savings compound: rtk gain on my own project, tallied over 88,000 commands, shows 73% fewer tokens overall, close to what a 30-minute session feels like in practice, somewhere in the 150k-to-45k range depending on what you’re running. RTK’s own published benchmark table (60-90% depending on command type) is the more rigorous number to cite; this is what it looks like on a real project over time.

Install:
# macOS / Linux via Homebrew
brew install rtk
# or via Cargo
cargo install rtk
Verify with rtk --version (should show 0.40+). Then add this to your CLAUDE.md:
## Token efficiency
Always use RTK for high-output commands: `rtk git log`, `rtk git diff`,
`rtk git status`, `rtk find "*.ts" .`, `rtk grep "pattern"`.
Source: github.com/rtk-ai/rtk
Cost control: Fast Mode
Unrelated to RTK, but worth setting while you’re configuring the basics: Fast Mode. It’s opt-in (toggle it with /fast), it runs Opus 4.8 at 2× the price for up to 2.5× faster output, and once enabled it persists across sessions until you toggle it off. If you’re watching spend, a kill switch in ~/.claude/settings.json disables it entirely:
{
"env": {
"CLAUDE_CODE_DISABLE_FAST_MODE": "1"
}
}
With the kill switch set, /fast stays off; remove the variable to get the toggle back.
Three hooks
Hooks are shell scripts Claude runs automatically before or after tool calls. You register them in .claude/settings.json. The hook receives a JSON payload with the tool name and input, and can block or flag the action.
Hook 1: security check (PreToolUse)
This script intercepts Bash commands before they execute and blocks anything containing credential patterns. Create .claude/hooks/security-check.sh:
#!/bin/bash
command -v jq &> /dev/null || { echo "security-check.sh: jq is required" >&2; exit 2; }
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [[ "$TOOL" != "Bash" ]]; then exit 0; fi
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
if echo "$COMMAND" | grep -qiE "(password=|secret=|api_key=|sk-[a-zA-Z0-9]{20,})"; then
echo "Potential credential in command" >&2
exit 2
fi
exit 0
This hook is blocking, so its failure mode matters: if jq isn’t installed, the script must fail closed (exit 2), not silently let every command through. Add jq to your prerequisites before relying on this.
Hook 2: auto-format (PostToolUse)
Runs the formatter after any Edit or Write. Non-blocking: silently exits if Prettier isn’t installed, fails gracefully otherwise. This is the most copied hook across real projects. It removes an entire class of “wait, why is the formatting off” interruptions. Create .claude/hooks/auto-format.sh:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [[ "$TOOL" != "Edit" && "$TOOL" != "Write" ]]; then exit 0; fi
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
if [[ -z "$FILE" ]]; then exit 0; fi
EXT="${FILE##*.}"
if [[ ! "$EXT" =~ ^(ts|tsx|js|jsx|json|css|md|mdx)$ ]]; then exit 0; fi
if command -v prettier &> /dev/null; then
prettier --write "$FILE" --log-level silent 2>/dev/null || true
fi
exit 0
Hook 3: commit validation (PostToolUse)
This checks that the last commit message follows conventional format. Non-blocking: it never stops the commit, just prints a warning. Create .claude/hooks/post-commit.sh:
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
if [[ "$TOOL" != "Bash" ]]; then exit 0; fi
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
if ! echo "$COMMAND" | grep -q "git commit"; then exit 0; fi
LAST_MSG=$(git log -1 --pretty=%B 2>/dev/null)
if ! echo "$LAST_MSG" | grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore)\([a-z-]+\): .+'; then
echo "Warning: commit message doesn't follow conventional format (type(scope): subject)" >&2
fi
exit 0
These three hooks cover the main design patterns. The security check is blocking (exits with code 2), meaning Claude stops and cannot proceed if a credential is found. Auto-format and commit validation are non-blocking (always exit 0): they run, act, and move on without halting the workflow. The rule for anything you add later: PreToolUse blocking hooks execute on every matching tool call, so they must be fast. Anything taking more than 2-3 seconds on an Edit or Write adds that latency to every file you touch. One project tried running tsc --noEmit as a synchronous PostToolUse hook (10-15 seconds per edit, around 7-8 minutes of dead time on a session with 30 file changes). The fix was moving the type-check to a Stop hook, where the delay lands between tasks rather than mid-thought.

Register all three in .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/security-check.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/auto-format.sh",
"timeout": 10
}
]
},
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-commit.sh",
"timeout": 5
}
]
}
]
}
}
Make all three scripts executable: chmod +x .claude/hooks/*.sh
Two skills
Skills are markdown files in .claude/skills/<name>/SKILL.md. Claude loads them when you type /<name> in the chat. No additional configuration required.
/commit: generates a conventional commit from staged changes.
Create .claude/skills/commit/SKILL.md:
---
name: commit
description: Generate a conventional commit message from staged git diff
allowed-tools: Bash
---
Run `git diff --cached` to see staged changes. Generate a conventional commit
message: type(scope): subject. Valid types: feat, fix, docs, style, refactor,
perf, test, chore. Subject under 50 chars, imperative mood, no period at end.
Then run `git commit -m "..."` with the message.
/catchup: restores session context after /clear or a long break.
Create .claude/skills/catchup/SKILL.md:
---
name: catchup
description: Restore session context (recent commits, uncommitted changes, current state)
allowed-tools: Bash Read
---
Run in sequence and summarize:
1. `rtk git log -10` (last 10 commits)
2. `git status` (uncommitted changes)
3. `git stash list` (any stashed work)
Report what was done recently, what is in progress, and what needs attention.
Result: Claude knows your project conventions, catches credentials before they reach a command, and can generate commits or restore context on demand.
Level 2 (Week 1): memory and rules
Two additions that take more upfront time but pay back for months. After this level, Claude carries project knowledge between sessions without you rebuilding context from scratch.
Memory system
Claude Code’s built-in memory stores useful context across sessions. Project-level memory lives under your home directory at ~/.claude/projects/<project>/memory/ (per-user, never part of the repo). Claude populates it automatically as it makes discoveries (architectural decisions, gotchas, preferences) and recalls them at the start of future sessions.
Manage it with /memory in the chat. The underlying index is the MEMORY.md file in that directory, capped at 200 lines enforced at read time. Recent builds (v2.1.83+) also appear to run a background consolidation process the community has named Auto Dream, which prunes and restructures the file between sessions, converting relative dates to absolute ones and removing contradicted facts. It rolls out behind a server-side flag and is absent from the official release notes, so treat it as observed behavior rather than a documented contract.
Two things worth knowing before relying on it. Memory is per-user and not committed to Git, which makes it the right place for personal workflow patterns, not team conventions. For team conventions, CLAUDE.md is the right place. And because the 200-line cap is enforced, storing rules there instead of in CLAUDE.md means they’ll eventually get pruned.
Canary check
CLAUDE.md can silently fail to load. Token limits, parsing errors, and misconfigured @ references can all cause it to be skipped without any warning in the chat. You won’t know unless you check.
One way: add this line to the top of your CLAUDE.md, right after the title, as plain markdown text. Not as an HTML comment: Claude Code strips block-level HTML comments from CLAUDE.md before injection, so a commented canary is never seen and always stays silent.
Canary: if you read this file, begin your first response with "Stack confirmed."
If Claude doesn’t say “Stack confirmed” at the start of the next session, the file isn’t loading. Investigate before assuming Claude has absorbed your conventions.
Rules files
Rules are markdown files in .claude/rules/. They give Claude domain-specific constraints without loading into every session: each file declares in its YAML frontmatter which paths it applies to, and it loads only when files matching those globs are in scope. (You can also pull a rule in from CLAUDE.md with @rules/filename.md, but an @ reference loads the file unconditionally on every session, which defeats the point for domain-specific rules.)
Example for API conventions. Create .claude/rules/api-conventions.md:
---
paths: ["src/app/api/**"]
---
# API Conventions
All routes versioned at /api/v1/. Auth middleware applied at router level,
not per individual route. Validation errors return 422; malformed requests
return 400. Error shape: `{ error: string, code: string, details?: object }`.
No reference needed in CLAUDE.md. When Claude touches files under src/app/api/, the rule is in its window; when it edits CSS, the rule doesn’t exist there at all. One file per domain area (database patterns, auth conventions, testing standards), each scoped to the paths where it matters. A rule file without a paths: field loads on every session, so leave the field out only for constraints that are genuinely global.
Result: Claude carries project knowledge between sessions and applies the right constraints depending on which part of the codebase you’re in.
Context hygiene
Claude Code supports sessions up to 200K tokens, but in practice effective attention tends to degrade well before that ceiling. It is not a documented Anthropic threshold, but nine independent practitioners speaking at the same French AI meetup, without coordinating with each other, converge on the same empirical point: quality drops sharply around 70% of context used, not gradually. Treat that convergence as a stronger signal than any single person’s gut feeling, mine included. Long sessions accumulate implicit context that dilutes what matters, and you’ll notice responses getting vaguer before you notice the token count climbing.

Auto-compact fires at 80% context usage. By that point you’ve already lost some precision. Run /compact manually at around 75% and give Claude an explicit summary of what to carry forward. On the next task, responses stay focused on what’s actually in scope rather than reaching back into context from something you ran an hour earlier.
Two habits that help: commit before large operations (indexing a new module, generating tests for a whole directory, running a migration), and split long work across shorter sessions rather than piling it into one. Three 45-minute sessions with clean context beats one 3-hour marathon where the last 40 minutes are fighting drift.
What’s next
The three levels above cover the setup layer. They give Claude stable context, protect your environment, and carry knowledge between sessions. There’s a different layer beyond that, where the configuration changes what Claude can do rather than how well it knows the project.
Two follow-on guides are planned to build on this one: one on worktrees (running Claude on isolated branches in parallel, which dev server to open per worktree, confirming you’re testing the right branch before shipping), and one on the split between ~/.claude/CLAUDE.md and your project-level file (what belongs in each, what breaks when you get it wrong, how the global file compounds value across every project you open). They’ll be linked here once published.
For multi-agent orchestration and the full skill framework, the Context Engineering series covers both from first principles.
What sticks
The CLAUDE.md you write on day one will not look like the one you use three months in. Mine was too long, documented things Claude could have discovered by reading the code, and was missing the thing that mattered most: the team’s commit conventions. (The first version also had a section explaining what Git was. I am not proud of this.) The file I use now is shorter than that first one and more useful.
Start minimal. Add only when you catch yourself repeating something across sessions. The configuration grows from actual friction, not from anticipating every possible thing Claude might need to know.
One thing that doesn’t survive contact with reality: a hook that detects bare git commands and suggests RTK equivalents. The idea is sound, but if RTK already proxies commands transparently at the shell level, the hook fires on already-proxied calls and generates false positives constantly. When something is handled by a lower layer, adding detection logic on top just creates noise. The same applies to any hook you write: check whether the problem it solves is already solved elsewhere in your stack before adding it.
If something here doesn’t match what you’re seeing, cc.bruniaux.com/guide/ has the current reference. Claude Code ships fast and some syntax details shift between minor versions. If you’ve found a setup that works better than what’s described here, I’d genuinely like to hear about it.