I used Claude Code daily for six months before I admitted the obvious: the defaults were costing me 10–15 hours a month. Not because the tool is bad — because I never configured it. The agent would wander off, forget decisions mid-session, or confidently rewrite code I hadn't asked it to touch, and I'd clean up after it.
This isn't a "what is Claude Code" tutorial. It's the settings I landed on if you already use it and want more out of it. Each section ends with copy-paste config.
1. Cap CLAUDE.md at 8,000 characters
My biggest early mistake was dumping everything into CLAUDE.md. By March it was 40,000 characters. I assumed more context meant better output. Wrong — and the reason is structural: transformers are bad at retaining info in the middle of long documents. Past a certain size the agent stops seeing middle-of-file rules. I asked it to reproduce a rule from line 300 of its own CLAUDE.md; it couldn't.
Hard cap the main file at 8,000 characters. Everything else goes into files loaded on demand:
.claude/
├── CLAUDE.md # always-on context, ≤ 8,000 chars
├── skills/
│ ├── nestjs.md # NestJS session rules
│ ├── migration.md # TS migration rules
│ └── testing.md # test strategy
└── specs/
├── domain-model.md
├── api-contracts.md
└── infra.md
CLAUDE.md holds only what every session needs: stack, hard prohibitions, links to specs and skills. After the refactor: 6,000 chars, more accurate answers, ~35% lower session-init token cost.
2. settings.json — the permissions that change your rhythm
Most devs never open settings.json. Add this and read/test commands stop interrupting you:
{
"permissions": {
"allow": [
"Bash(git log:*)",
"Bash(git diff:*)",
"Bash(git status:*)",
"Bash(npm test:*)",
"Bash(npm run lint:*)",
"Bash(npm run typecheck:*)"
],
"deny": [
"Bash(git push:*)",
"Bash(git reset --hard:*)",
"Bash(rm -rf:*)"
]
}
}
Interruptions per session went from 15–20 to 2–3. deny outranks allow, so dangerous commands stay blocked. Commit .claude/settings.json and the whole team gets the same guardrails.
3. acceptEdits — enable it selectively, not globally
acceptEdits removes per-edit confirmations. It helps for a specific class of work:
Enable for: type migrations (JS→TS, fixing any), mechanical fixes (renames, import swaps, formatting), test generation against existing logic, docs/comments.
Don't enable for: new features, architectural refactors, auth/payment/data-layer changes, or any task where you can't state the "done" criteria in 10 seconds.
Real example: I enabled it for "clean up unused imports." The agent tidied the imports, then decided to rewrite a few services while it was in there. Valid, worse than our patterns, 30 unplanned minutes of review. My rule: acceptEdits only when automated tests can catch the mistake.
4. Hooks as guardrails — the 3 that earned their keep
Hooks are the most underrated feature. Shell scripts that run before/after tool calls, receiving JSON context on stdin.
Stop hook — log every session:
#!/bin/bash
# ~/.claude/hooks/stop.sh
TASK=$(cat /tmp/claude_current_task 2>/dev/null || echo "no task recorded")
echo "$(date '+%Y-%m-%d %H:%M') | $TASK" >> ~/.claude/session_history.log
A month of this gave me an honest report: ~2h15m/day with the agent, 4–5 sessions. I thought I used it "occasionally." The data disagreed.
PreToolUse hook — block dangerous commands. Return exit 2 to block and pass stderr back to the agent:
#!/bin/bash
# ~/.claude/hooks/pre_tool.sh
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
DANGEROUS="(rm -rf|DROP TABLE|truncate|DELETE FROM.*WHERE 1=1|git reset --hard|kubectl delete)"
if echo "$COMMAND" | grep -qiE "$DANGEROUS"; then
echo "BLOCKED: potentially dangerous command: $COMMAND" >&2
exit 2
fi
exit 0
Fired 7 times in 3 months. 5 were reasonable (I ran them manually after review); 2 I was glad it caught.
Notification hook — ping when done:
#!/bin/bash
osascript -e 'display notification "Claude Code finished the task" with title "Claude Code"'
# Linux: notify-send "Claude Code" "Task done"
Wiring them up:
{
"hooks": {
"Stop": [{ "matcher": "", "hooks": [{ "type": "command", "command": "~/.claude/hooks/stop.sh" }] }],
"PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "~/.claude/hooks/pre_tool.sh" }] }]
}
}
5. Multi-model council for architecture decisions
For hard-to-roll-back decisions (module layout, data schema, service contracts), I run the same task across models in parallel:
Opus: "Design [X]. Priority: reliability and explicit contracts"
Sonnet: "Design [X]. Priority: development speed"
Haiku: "Design [X]. Minimal complexity, minimal abstraction"
Then a fourth Opus session synthesizes the best of the three against our context. ~20–25 minutes. On my last big refactor it replaced a week of team debate and landed cleaner than a 2–3 person discussion would have.
6. "Effort levels" — match depth to the task
I formalized this as a custom skill with four levels. (/effort is my skill, not built in; the built-in way to deepen reasoning is keywords like think / ultrathink.)
/effort low fast draft, no plan, just code → small utilities, throwaway
/effort medium short plan, then implement → standard features, fixes
/effort high detailed plan, then implement → new components, public API changes
/effort max analyze → plan → approval → implement → report; stop for OK at each step
→ core changes, auth, schema migrations
Zero full rollbacks on /effort max in six months. Bonus: if /effort max feels like overkill for a task, the task is easier than you thought. The reverse holds too.
7. Spot context rot early and restart
Long sessions degrade. Three tells:
- The agent re-proposes something you already rejected this session
- It suggests changing code it just wrote
- It asks you to "clarify the task" you gave 10 messages ago
Don't push through. Restart with a short handoff:
# Context to continue
**Task:** [one sentence]
**Done:** [file/function]: [what], ...
**Ruled out (discussed):** [option]: [why], ...
**Next step:** [one concrete item]
3–5 minutes to fill out, 5–10x faster than dragging a rotted session. My personal trigger: 3 messages with no forward progress → restart, no debate.
8. The two-correction rule
If you correct the agent twice on the same point in one session, stop. That's a signal the task framing or context is wrong, not that the agent is stubborn. Rebuild the task three ways:
- Split smaller. Two failures on X may mean X is too big. Solve X1 then X2.
- Give a concrete example of the desired output instead of "do it right."
- Write the skeleton (structure, signatures) and let the agent fill it in. "Here's the service interface, implement it" beats "write a service for X."
Estimated 15–20 hours saved over six months.
9. Split settings by environment
Settings merge hierarchically: team .claude/settings.json, personal .claude/settings.local.json (gitignored), user-global ~/.claude/settings.json.
Team (strict, committed):
{ "permissions": { "deny": ["Bash(kubectl delete:*)", "Bash(terraform destroy:*)", "Bash(rm -rf:*)"] } }
Personal (local, gitignored):
{ "permissions": { "allow": ["Bash(docker compose up --build:*)", "Bash(npm run db:reset:*)"] } }
You move fast locally without losing the shared guardrails — deny outranks allow, so a local allow can't override a team deny. You can also switch permission modes at launch (--permission-mode plan) for investigation-only sessions.
10. Skills: lazy-load project-specific context
Skills are markdown files in .claude/skills/, loaded on demand. The point: don't keep project-specific context in the agent's memory full-time.
.claude/skills/
├── nestjs-conventions.md
├── clean-arch.md
├── typescript-strict.md
├── git-workflow.md
├── testing-strategy.md
└── code-review-checklist.md
Each maps to a task type — writing a NestJS module → nestjs-conventions, refactoring → clean-arch, reviewing → code-review-checklist. After moving to skills: main CLAUDE.md from 40K to 6K chars, 30–40% lower token use in standard sessions, and the agent stopped mixing rules from different domains.
Where this leads
Once you're running multiple models in parallel (#5), the next question isn't prompt quality — it's routing and cost: who uses which model, and what does each workflow cost? That part lives outside Claude Code. Routing the CLI through a gateway gives you per-workflow, per-model cost visibility across providers; the EvoLink Claude Code CLI guide documents that setup if your team's at that stage.
Takeaway
Claude Code isn't plug-and-play — it's a platform you configure. Defaults run at "fine"; the right settings move it to "genuinely fast." By my estimate these paid back 50–60 hours in the first six months. If you've got settings that didn't make my list — especially hooks — drop them in the comments.
tags: claude-code, ai, developer-tools, productivity
Top comments (0)