Claude Code Has a Configuration System. You Should Use It.


I’ve been using Claude Code for about a year now. For most of that time, I used it the way most people do: open a session, type what I want, iterate on the output. Vanilla prompting. It worked fine. Claude is smart enough that you can get a lot done just by talking to it.

A couple of months ago I started actually customizing my setup. Custom skills, MCP servers, hooks, project-specific instructions. The difference was immediate. Tasks that used to take multiple rounds of back-and-forth now happen in one command. Context I used to re-explain every session is just there. Claude went from a capable tool to something that genuinely knows how I work.Anthropic’s .claude directory reference and their blog post on CLAUDE.md files are the official starting points. This post is a more opinionated companion: how I think about the whole system, and what my actual config looks like.

Here’s the kind of difference I mean:

12 msgs·6 corrections
>Add error handling to the payment endpoint.
I'll add a try-catch block with error logging to handle failures gracefully.
>We use AppError for all errors, not generic Error. And log through the structured logger, not console.log.
Updated. Replaced the generic Error with AppError and switched to the structured logger.
>Handlers go in src/api/handlers/. Kebab-case filenames.
>The response needs to match our API envelope: { ok, data, error }.
>OK, now review what you just wrote. Check for bugs and make sure it's consistent with the rest of the codebase.
Looking at the code... the error handling follows standard patterns. Looks good to me.
>No, check the error handling in the other endpoints too. And verify the response format matches our OpenAPI spec.
Let me look at the other handlers... I found a few inconsistencies with how the other endpoints structure their error responses.
>Can you check if the error page renders correctly in the browser?
I don't have access to a browser. Could you take a screenshot and share it with me?

Same work, three interactions instead of twelve. Each one powered by a different piece of config. This post walks through all of them.


Where it all lives

All of Claude Code’s configuration lives in two places: ~/.claude/ in your home directory (personal, never touches git) and .claude/ inside each project (committed, shared with the team).

One global config, two different projects
Show session:
~/
.claude/Global (personal)
CLAUDE.md
settings.json
skills/
suggest/
review/
diagram/
···
projects/Auto-generated
···
marketing-site/Committed (team)
CLAUDE.md
CLAUDE.local.mdLocal (gitignored)
.mcp.json
.claude/
settings.json
settings.local.jsonLocal (gitignored)
skills/
rules/
api-server/Committed (team)
CLAUDE.md
.claude/
settings.json
skills/
rules/
Global (personal)
Committed (team)
Local (gitignored)
Auto-generated

Stop re-explaining your project

Every session, before you type a single word, Claude reads all CLAUDE.md files in scope and holds them in context. Project conventions, design system rules, deployment workflows: they become things Claude knows without being told, across every session, including after /compact.CLAUDE.md content is injected as a <system-reminder>into every request, not just the first. When context gets compressed, it’s re-read from disk and re-injected fresh. See the Memory docs for the full reference.

But every line costs context on every turn. Claude reliably follows roughly 150 instructions, and the system prompt already uses about 50 of those slots. A bloated CLAUDE.md doesn’t degrade selectively (ignoring only irrelevant rules), it degrades uniformly across allrules. The practical test for any line: would removing it cause Claude to make a mistake it wouldn’t make otherwise? If not, cut it. In particular, never use CLAUDE.md for code style rules. Formatting belongs in a hook that runs Prettier or Biome after every file edit. Never send an LLM to do a linter’s job.If you need a quick answer without polluting your conversation history, use /btw. It answers in a dismissible overlay that never enters context.

There are three scopes. Global (~/.claude/CLAUDE.md) applies in every project. Project (CLAUDE.md at the repo root) is where the bulk lives. Local (CLAUDE.local.md) is the gitignored escape hatch for personal notes.

~/.claude/CLAUDE.md — applies in every project
# Global Claude Instructions

## Shell Commands
- Always quote paths with double quotes in shell commands,
  never use backslash-escaped spaces.

## Browser Automation
- If browser automation is needed and chrome-devtools is
  not yet set up in this project, run /setup-browser first.
- Use the chrome-devtools MCP for all browser automation:
  navigation, clicking, form filling, screenshots, and
  screen recording.

Anthropic recommends keeping each file under 200 lines. When your project CLAUDE.md gets long, you can split it into .claude/rules/, where each file is a standalone rule that loads either unconditionally or only when Claude opens matching files. Path-scoped rules use YAML frontmatter:

.claude/rules/testing.md
---
paths:
  - "**/*.test.ts"
  - "**/*.test.tsx"
---

# Testing Rules

- Use descriptive test names: "should [expected] when [condition]"
- Mock external dependencies, not internal modules
- Clean up side effects in afterEach

This rule only loads when Claude opens a test file. Your main CLAUDE.md stays lean, and the testing conventions are right there when they’re relevant.You can also put a CLAUDE.md in any subdirectory (e.g. src/api/CLAUDE.md). It loads on demand when Claude works with files in that directory, not at session start. Useful for monorepos where different packages have different conventions.


Build workflows, not prompts

CLAUDE.md handles what Claude should know. Skills handle what Claude should do. Each skill is a directory with a SKILL.md file: a markdown prompt that Claude executes when you type its slash command. Type /review and two subagents spin up to check your code. Type /demoand a video gets recorded. The interaction shifts from “tell Claude what to do each time” to “build the workflow once, then trigger it.”Full reference at code.claude.com/docs/en/slash-commands.

Global skills (~/.claude/skills/) are available in every project. They all follow the same pattern: a workflow I used to do by prompting, now triggered with one command:

Here’s what a skill actually looks like. This is my /review SKILL.md:

~/.claude/skills/review/SKILL.md
---
name: review
description: Spawn parallel review agents to independently audit recent work for bugs, logic errors, and issues before committing
disable-model-invocation: true
effort: high
---

Review all work done in this session. Spawn two independent review agents that
each examine the changed files from different angles, then synthesize their
findings.

## What to review

Look at all files that have been modified or created in this session. If unclear,
use the uncommitted changes and recent commits as a guide:

- Uncommitted: !`git diff --name-only`
- Staged: !`git diff --cached --name-only`
- Untracked: !`git ls-files --others --exclude-standard`
...

It’s just markdown. The frontmatter sets metadata (name, description, effort level). The body is a prompt that tells Claude exactly what to do when you type /review. The ! backtick syntax runs shell commands inline, so the skill can inspect your actual git state before deciding what to review. One pattern worth trying: a spec-first skill that asks Claude to interview you about requirements before writing any code, saves the spec to a file, then you start a fresh session to implement it. The fresh session has clean context focused on implementation, and the spec file is the source of truth.

Project skills (.claude/skills/) are specific to a repo. For this blog, I have a create-visualization skill that encodes the entire design language (color tokens, element patterns, animation specs, 16 explicit “do not” rules learned from past mistakes). There’s also adapt-blog-post for converting Obsidian markdown into the blog’s React page format, and frontend-design for when I’m building new components. If you want to browse what other people have built, skills.sh is a community directory where you can find and install skills directly.

The split is intuitive. If a skill is about how you work, it goes in ~/.claude/skills/. If it’s about how this project works, it goes in .claude/skills/.


Give Claude eyes and hands

CLAUDE.md tells Claude what to know. Skills tell it what to do. MCP servers and hooks give it access to things it can’t reach on its own: browsers, databases, external APIs, and deterministic actions that run every time.

MCP: external tools

MCP (Model Context Protocol) connects Claude to external tools. Project servers go in .mcp.json at the repo root. Personal servers go in ~/.claude.json. Both are available in the session simultaneously. Use ${ENV_VAR} syntax in .mcp.json for secrets, since that file is committed.Full reference at code.claude.com/docs/en/mcp.

For this blog, I have chrome-devtools configured as a project MCP server for browser debugging and screenshots. In another project, I run a database inspector. Each project gets exactly the tools it needs.

Hooks: deterministic actions

Hooks are scripts that fire at lifecycle events (session start, after a tool use, when Claude stops). Unlike CLAUDE.md guidance, hooks are deterministic: they always run. A hook that blocks rm -rf with exit code 2 is physically impossible to circumvent.One gotcha: exit code 1 is a non-blocking notice, exit code 2 actually blocks the action. Most people write exit 1thinking it blocks, and it doesn’t. Full reference at code.claude.com/docs/en/hooks.

I have a global SessionStart hook that detects when Claude launches inside a git worktree and renames the terminal tab to match. When you’re juggling multiple agents across different worktrees, being able to glance at a tab name and know which one is which makes a real difference:

A SessionStart hook detects worktree launches and renames the terminal tab automatically.

The infrastructure underneath

Settings control the defaults that make everything above work without friction. Three settings.json files, from highest to lowest precedence: .claude/settings.local.json (personal, gitignored), .claude/settings.json (team, committed), ~/.claude/settings.json (global defaults). Scalar settings are overridden (most specific wins). Array settings like permissions.allow accumulate across all scopes.Setting "defaultMode": "auto" in your permissions lets Claude execute without prompting for each tool use. This requires the --enable-auto-mode flag and is only available with API billing. See the announcement.Full reference at code.claude.com/docs/en/settings. The features overview has a comparison table of CLAUDE.md vs skills vs hooks vs MCP.

~/.claude/settings.json
{
  "model": "opus[1m]",
  "permissions": {
    "allow": [
      "Bash(git add *)", "Bash(git commit *)",
      "Bash(git log *)", "Bash(git diff *)",
      "Bash(git status *)", "Bash(npm *)",
      "Bash(npx *)", "Bash(ls *)",
      "Read(/Users/yourname/**)",
      "WebSearch"
    ],
    "deny": [
      "Bash(*npm install*)",
      "Bash(*bun add*)",
      "Bash(*push --force*)",
      "Bash(*reset --hard*)"
    ]
  },
  "hooks": {
    "SessionStart": [{
      "hooks": [{
        "type": "command",
        "command": "bash ~/.claude/hooks/cwt-rename-hook.sh"
      }]
    }]
  }
}

Every shared config file has a .local counterpart with higher precedence. Want a different model than the team default? Put it in settings.local.json. Personal workflow notes? CLAUDE.local.md. None of it leaks into git. For team projects, add these to your .gitignore:

You don’t need all of this at once. Run /init in your project today: it analyzes your codebase and generates a starter CLAUDE.md. The rest accumulates naturally, correction by correction into CLAUDE.md, workflow by workflow into skills, project by project into MCP servers. The configuration grows with your usage, never faster than you need it.Set CLAUDE_CODE_NEW_INIT=1 for an interactive flow that also sets up skills and hooks, not just CLAUDE.md.

If you found this helpful, share it:

Share on X
Further reading