i spent three weeks using Claude Code before i realized i was doing it wrong.
not wrong like “it doesn’t work.” wrong like “i was typing the same instructions every single session, approving the same permissions over and over, and watching it forget my preferences the moment i closed the terminal.” i was treating Claude Code like a chatbot instead of a tool i could actually configure.
once i figured out the config system, everything changed. so here’s what i wish someone had told me on day one.
two folders, one system
Claude Code’s configuration lives in two places. understanding the split is the single most important thing to get right before you customize anything.
global (~/.claude/) is your personal defaults. these follow you everywhere. no matter what project you open, your global config comes with you.
project (.claude/ inside any repo) is specific to that particular project. everyone working on that project follows these rules, regardless of what’s in their personal config.
there’s also a local override layer (.claude/settings.local.json) that lets you make personal tweaks within a project without affecting anyone else. i use this constantly for testing new permissions before committing them to the team config.
what goes where
this is the part that confused me for weeks. here’s how i think about it now.
your global ~/.claude/ folder is where you put things about you. your coding style. your preferred tools. your communication preferences. the stuff that doesn’t change from project to project.
your project .claude/ folder is where you put things about the project. the architecture. the build commands. the rules that anyone working on this codebase needs to follow.
simple once you see it. But i didn’t see it for a while.
the files that matter
CLAUDE.md (the memory file)
this is the simplest and most useful file in the whole system. it’s plain markdown that Claude reads at the start of every session. think of it as the brief you’d hand someone on their first day.
global (~/.claude/CLAUDE.md) applies to all projects:
## who i am
construction professional turned builder-coder.
i prefer direct, practical language over jargon.
i work in Python, JavaScript, and Astro.
## how i work
- be concise. skip disclaimers.
- show me working code, not abstract patterns.
- if something is risky, say so directly.
## code standards
- clean, readable code over clever code
- meaningful commit messages
- handle errors explicitly
project (CLAUDE.md at repo root) is specific to one codebase:
## what this is
astro site deployed to Cloudflare Pages.
## architecture
- blog posts in src/data/blog/ as markdown
- components in src/components/
- tests alongside source files
## rules
- all lowercase in titles and headings
- npm run dev to start, npm run build to deploy
you can also use #include syntax to pull in other files, which keeps things modular when your CLAUDE.md starts getting long.
settings.json (the control panel)
this is where you configure permissions, environment variables, and hooks. the structure is identical at every scope. what changes is the file location.
here’s a practical global config:
{
"permissions": {
"allow": [
"Bash(npm run *)",
"Bash(git *)",
"Bash(python3 *)",
"Read",
"Write",
"Edit",
"Glob",
"Grep"
],
"deny": [
"Read(./.env)",
"Read(./.env.*)",
"Bash(rm -rf *)",
"Bash(sudo *)"
]
},
"env": {
"DISABLE_TELEMETRY": "1"
}
}
the allow list pre-approves commands so you don’t get prompted every time. the deny list blocks things outright. i keep rm -rf and sudo on the deny list globally because i don’t want Claude running those under any circumstances.
permission rules use pattern syntax with wildcards. Bash(npm run *) means “allow any npm run command.” Read(./.env) means “never read my .env file.” takes five minutes to set up, saves you hundreds of permission prompts.
the precedence hierarchy
when settings conflict, here’s who wins (highest to lowest priority):
- managed (enterprise/org policies, if you have them)
- local project (
.claude/settings.local.json) - project (
.claude/settings.json) - global (
~/.claude/settings.json)
so if your global settings allow curl but the project settings deny it, the project wins. but if you add an allow rule to .claude/settings.local.json, the local override beats the project setting.
i use this for experimentation. when i’m trying new permissions or hooks, i put them in settings.local.json first. it’s auto-gitignored and won’t affect anyone else.
commands, agents, and skills
these three are the extension points that turn Claude Code from a chatbot into a real tool. they’re different from each other in ways that matter.
commands (saved prompts)
commands are markdown files that become slash commands. the filename becomes the command name. they’re the simplest extension.
~/.claude/commands/review.md:
review the code i just changed. focus on:
1. bugs or logic errors
2. missing error handling
3. anything that would bite me in production
skip style nitpicks unless something is genuinely confusing.
now i type /review and get a focused code review every time. no retyping the instructions. $ARGUMENTS works as a placeholder too, so /estimate the auth flow passes “the auth flow” into the prompt.
agents (isolated workers)
agents are like commands but they run in their own context window. this is the difference that matters: they don’t pollute your main session’s context. use agents for tasks that require deep analysis or that you want to run in parallel.
~/.claude/agents/research-agent.md:
---
name: research-agent
description: research a topic and return a structured summary
model: sonnet
allowed-tools:
- WebSearch
- WebFetch
- Read
---
you are a research assistant. when given a topic:
1. search for current, authoritative sources
2. cross-reference at least 3 sources
3. return a structured summary with key findings and source URLs
4. flag anything uncertain
the frontmatter controls which model runs the agent, what tools it can use, and when Claude should invoke it automatically.
skills (the power feature)
skills are the most capable extension point. unlike commands (just prompts) and agents (isolated workers), skills are instruction packages that can bundle scripts, templates, and reference materials. Claude loads them on-demand when they’re relevant.
the difference: skills are folders with a SKILL.md file inside. not a single file. this trips everyone up.
~/.claude/skills/proposal-writer/
├── SKILL.md # the skill instructions
├── scripts/
│ └── calc.js # bundled calculator
├── templates/
│ └── proposal.md # document template
└── references/
└── checklist.md # scope checklist
the SKILL.md frontmatter tells Claude when to trigger the skill automatically, what tools it can use, and whether it runs in-conversation or as a subagent. skills can also include executable scripts in any language.
i have skills for writing blog posts, generating project plans, and building MVPs. each one bundles everything Claude needs to do the job without me re-explaining the context every session.
hooks (the enforcement layer)
this is the part most people skip. And it’s the part that matters most.
CLAUDE.md instructions are suggestions. Claude follows them most of the time, but it can forget or drift. hooks are scripts that fire at specific lifecycle points, and they run every single time. no exceptions.
hooks are configured in settings.json under the hooks key:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/block-destructive.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/format-on-save.sh"
}
]
}
]
}
}
PreToolUse runs before Claude uses a tool. your hook can block the action entirely by returning exit code 2. PostToolUse runs after, which is good for auto-formatting or linting. there’s also SessionStart (when a session begins) and Stop (when Claude finishes responding).
the hook scripts read JSON from stdin and return a decision. exit code 0 means allow. exit code 2 means block. your stderr message gets shown to Claude so it knows why.
one note: most hook scripts use jq to parse JSON input. install it with brew install jq on macOS if you don’t have it.
MCP servers (external tools)
MCP (Model Context Protocol) servers give Claude access to external services. the config lives in .mcp.json files at two scopes.
global (~/.mcp.json, at your home directory root, NOT inside ~/.claude/):
{
"mcpServers": {
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"]
}
}
}
project (.mcp.json at your repo root):
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
use ${ENV_VAR} syntax for secrets. never hardcode tokens in files you commit. set the actual values in your .zshrc or in the env section of settings.json.
you can also add MCP servers from the CLI without editing JSON:
claude mcp add github --command npx --args "-y @modelcontextprotocol/server-github"
start minimal here. each MCP server adds startup time and context. add them as you find real needs, not speculatively.
setting it up from scratch
here’s the order i’d follow if i were starting over today.
step 1: create the global structure.
mkdir -p ~/.claude/{commands,agents,skills,hooks}
step 2: write your global CLAUDE.md. who you are, how you work, what you expect. three to five sections, plain markdown. this is the single highest-value file in the whole system.
step 3: create your global settings.json with sensible permissions. allow the tools you use every day. deny the dangerous ones. takes five minutes.
step 4: add 2-3 global commands for things you do repeatedly. /review and /ship are good starting points.
step 5: add MCP servers only when you have a concrete need. memory is a good first one.
step 6: when you start a new project, run claude in the directory and use /init to generate a starter CLAUDE.md. then build out from there with project-specific commands, agents, and skills.
that’s it. don’t try to build out the entire hierarchy on day one. I built mine up over about two weeks, adding things as i noticed myself repeating work.
tips i learned the hard way
/status inside a Claude Code session shows you which settings files are active and where they’re loaded from. use this when something isn’t working and you can’t figure out why.
skills are folders, not files. ~/.claude/skills/estimate/SKILL.md, not ~/.claude/skills/estimate.md. i got this wrong three times before it stuck.
commands are being folded into skills. Anthropic has been consolidating the two. if you’re starting fresh, you can skip commands entirely and use skills for everything. But commands still work and are simpler for basic saved prompts.
CLAUDE.md supports #include. when your project memory gets long, break it into smaller files and include them. keeps things maintainable.
the MCP scope confusion is real. ~/.mcp.json is global. .mcp.json in your project root is project-specific. inside Claude Code, /mcp shows you what’s loaded and from where.
the real point
none of this config stuff is exciting. But it’s the difference between using Claude Code as a novelty and using it as a daily tool. i went from re-explaining my preferences every session to having Claude know exactly how i work from the first message.
if you’re a solo builder, a small business owner, or someone who just wants to get things done without fighting the tool, spend an hour on this setup. you’ll get that hour back in your first week.
ContractorKeith