A walkthrough of Claude Code from the ground up — what it is, how configuration scopes work, and the rules, plugins, hooks, auto memory, and CLI glue that make it click for day-to-day engineering work.

Claude Code is the tool I spend more time in than any IDE — Rust one hour, Python the next, TypeScript the hour after that. Hundreds of messages a day, across four or five projects. Over the past few months the configuration around it has turned into something much more than autocomplete: it enforces conventions, remembers context between sessions, and drives real infrastructure on my behalf. This post walks through that setup from the ground up. If you've never opened Claude Code before, the first half is for you. If you have, skip to the configuration sections.
Claude Code is Anthropic's official CLI. You run claude in a terminal and it opens an interactive agent that can read files, edit code, execute shell commands, search the web, talk to MCP servers, and delegate work to specialized subagents — all rooted in the directory you launched it from.
Under the hood it's the same Claude model that powers the API. What sets it apart from a chat window is everything around the model: tools, permissions, hooks, memory, plugins. The model is fixed; the shell around it is what you tune.
The mental model I use: Claude Code is a shell for an agent. The agent is smart. The shell is what you configure.
Four scopes, higher-priority locations winning on conflict:
| Scope | Location | Affects | Shared? |
|---|---|---|---|
| Managed | OS-level policy directory | All users on the machine | IT deploys |
| User | ~/.claude/ | You, across every project | No |
| Project | .claude/ + CLAUDE.md at repo root | Everyone in the repo | Yes (git) |
| Local | .claude/settings.local.json | You, in this repo only | No (gitignored) |
Everything below fits somewhere on this ladder. A rule that applies to every project goes in user scope. A convention that only makes sense in this repo goes in project scope. A machine-specific override — a local path, a personal API key — goes in local scope. Managed is how organisations deploy company-wide policies that individuals can't override.
The separation matters more than it sounds. User-scope rules are how you stop repeating yourself. Project-scope rules are how you teach Claude what's non-obvious about this codebase without bloating every other session with it.
My user-scope directory looks like this:
~/.claude/
├── CLAUDE.md # Global instructions, loaded every session
├── settings.json # Permissions, hooks, plugins, status line
├── rules/ # Topic-scoped instruction files
├── skills/ # Custom workflows triggered by /commands
├── projects/ # Per-project auto memory (managed by Claude)
│ └── <project>/memory/
│ ├── MEMORY.md
│ └── ...
├── statusline-command.sh # Custom status bar script
├── update-pricing.sh # Daily Anthropic pricing fetcher
└── cache/
└── model-pricing.env
The rest of the post walks through each of these in turn, roughly in the order you'd set them up yourself.
This is the file Claude reads at the start of every session. Think of it as a standing briefing: who you are, what you're working on, which conventions to respect.
You can have one at ~/.claude/CLAUDE.md (applies everywhere), one at ./CLAUDE.md or ./.claude/CLAUDE.md at the root of each repo (applies only there), and a personal ./CLAUDE.local.md that stays out of git. All discovered files are concatenated into context — they don't override each other. If you're starting a new project, /init inside Claude Code generates a first draft by reading your codebase.
Mine keeps the global version short. It's an index, not a manual:
main.rules/.The reason for keeping it short is that wall-of-text system prompts get ignored, both by the model and by future me when I go to edit it. The docs themselves recommend under 200 lines per CLAUDE.md. An index tells Claude where to look and keeps the actual content close to where it's relevant.
You can also pull in other files with @path imports — useful for referencing a README, a package.json, or a shared rules file from your home directory. Imports resolve recursively, so a project CLAUDE.md can pull in a personal preferences file without duplicating it.
Project-level CLAUDE.md files carry the things that are specific to a given codebase — the package manager, the content model, import aliases, testing commands. My personal website's looks something like:
pnpm (not npm).content/blog/.@/i18n/navigation (not next/link).cd into a different repo and the project-scope CLAUDE.md loads automatically, layered on top of the global one. Claude never needs to be re-briefed.
Claude Code has a dedicated directory for modular instructions: .claude/rules/ at the project level and ~/.claude/rules/ at the user level. Every .md file in there is discovered recursively and loaded alongside CLAUDE.md. It's a built-in feature, not a convention you have to bolt on.
The thing that makes rules genuinely useful over just extending CLAUDE.md: you can scope a rule to specific files via frontmatter. A rule with paths: ["src/api/**/*.ts"] only enters context when Claude is actually working on an API file. The rest of the time, it stays out of the way.
My user-level rule set is ten files, each focused on a single concern:
| File | What it covers |
|---|---|
coding-style.md | One concept per file, functions under 50 lines, "why" comments |
git-workflow.md | Imperative commits, feature branches, linter-before-commit |
security.md | No secrets in code, parameterised SQL, HTTPS only |
testing.md | Happy path plus edge and error cases, per-language frameworks |
rust.md | anyhow / thiserror, no unwrap(), the Service<R: Repository> pattern |
python-django.md | Django 5 + DRF, mssql-django, managed = False, uv over pip |
api-design.md | RFC 9457 errors, cursor pagination, rate-limit headers on all paths |
patterns.md | Repository pattern, per-layer error-handling strategy |
performance.md | Model selection, context budget, compaction strategy |
agents.md | When to delegate to subagents, parallel execution, plugin-to-task mapping |
Two things make this worth the setup effort:
CLAUDE.md and .claude/rules/ files are re-injected from disk after compaction.The payoff shows up in small ways every day. Claude runs cargo clippy before Rust commits, reaches for uv instead of pip, formats API errors in RFC 9457 shape — without me saying a word. Write the rule once, stop repeating yourself.
settings.json is the other half of the global config. It controls what Claude is allowed to do and what happens around its actions. Four things live here, each big enough to earn its own section.
Permissions split cleanly into an allow-list (auto-approved, no prompt) and a deny-list (hard-blocked, no override).
Allowed — what I want Claude to do without asking:
Read, Edit, Write, Glob, GrepBash, scoped by the deny-listWebFetch, WebSearchDenied — things that should never run, even if the model thinks they should:
rm -rf /, git push --force, git reset --hard, git clean -fdd, mkfs, shutdown, reboot, chmod 777npm publish, cargo publish, curl | bashcat ~/.ssh/*, cat *id_rsa*The deny-list is the single highest-leverage thing you can configure. A model that occasionally hallucinates dangerous commands is fine if those commands can never actually run. Set this up before you need it, not after.
For finer control, permissions accept argument-shape patterns — Bash(gh pr *) instead of opening Bash wholesale for gh. I use this when I want a CLI available but not trusted with arbitrary subcommands.
Hooks run shell commands around Claude's tool use. The docs list more than twenty events — PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, Stop, PreCompact, PostCompact, SubagentStart, FileChanged, and more. Each receives a JSON payload on stdin and can return structured output to block, modify, or just observe the call.
I use three of them.
A PreToolUse hook that nudges me before any Bash call:
{
"hooks": {
"PreToolUse": [{
"matcher": "Bash",
"hooks": [{
"type": "command",
"command": "echo '⚠️ Confirm branch and remote before pushing'"
}]
}]
}
}The matcher field is a tool name, a |-separated list, or a regex. PreToolUse can also block a call by returning {"hookSpecificOutput": {"permissionDecision": "deny"}}, which is how organisations enforce policy that goes beyond the static deny-list.
A PostToolUse hook that reminds Claude to lint after every edit. The payload arrives on stdin, so I extract the file path with jq:
FILE=$(jq -r '.tool_input.file_path // empty')
case "$FILE" in
*.rs) echo "Reminder: run cargo clippy" ;;
*.cs) echo "Reminder: run dotnet build" ;;
*.py) echo "Reminder: run ruff check && ruff format" ;;
esacNeither blocks the call — they just tap Claude on the shoulder. But they catch more than you'd think, because the reminder fires every time, not just when Claude remembers to ask.
A SessionStart hook gives me a moment to dump custom context — git status, branch staleness, a reminder about any in-flight experiment — into the very first message.
Plugins package skills, agents, hooks, MCP servers, and slash commands into a single installable unit. Claude Code ships with the official Anthropic marketplace (claude-plugins-official) already active, and you can add third-party marketplaces from GitHub repos, Git URLs, or local paths.
Installing a plugin is one command:
/plugin install github@claude-plugins-official
/plugin install supabase@claude-plugins-official
/plugin install vercel@claude-plugins-officialPlugin skills are namespaced by the plugin name (/github:create-pr), so two plugins can have skills with the same short name without colliding.
I run about a dozen. The official ones:
/commit, /commit-push-prPlus third-party:
Adding a third-party marketplace automatically happens through /plugin marketplace add, but you can also declare it in settings.json so teammates who trust the repo get prompted to install it:
{
"extraKnownMarketplaces": {
"interface-design": {
"source": {
"source": "github",
"repo": "Dammyjay93/interface-design"
}
}
}
}I keep a plugin-to-task mapping in rules/agents.md so Claude reaches for the right tool without being asked:
| Task | Plugin |
|---|---|
| Plan a feature | /feature-dev or Plan mode |
| Review code or PR | /code-review |
| Simplify code | /simplify |
| Git commit | /commit-commands:commit |
| Commit + push + PR | /commit-commands:commit-push-pr |
| Rust diagnostics | rust-analyzer-lsp |
| Browser testing | playwright |
Several of those plugins (github, vercel, supabase, context7, playwright) are really MCP servers in a wrapper — they query APIs and hand back structured data, which is good for reading state. For actually doing things — shipping a deploy, running a migration, cutting a release — I let Claude call the platform's native CLI through Bash.
The ones I use most:
gh — creating PRs, polling CI, merging. /ship is essentially a scripted sequence of gh calls.db diff, migrations, edge function deploys. The Supabase MCP handles introspection; the CLI handles changes.vercel deploy, env pull, log tailing. The plugin tells Claude what's going on; the CLI lets it act.No extra wrapping involved. The commands go through Bash, so the deny-list already blocks --force and the other destructive flags, and the PostToolUse hook still fires before anything mutating runs. When I want tighter control I allow-list a specific pattern like Bash(gh pr *) instead of opening up gh across the board.
The split I've settled on: MCP for reading, CLIs for writing, plugins for workflows that need both. Once that's written into the rules, Claude stops second-guessing which tool to reach for.
My favourite customisation. One line at the bottom of the terminal, showing everything that matters:
[Opus 4.6] misoto22-site | main | 45k 12k | 32% (57k/200k) | $0.45 | 12m30s | 3f +42 -8
Left to right: model, project directory, Git branch, tokens in and out, context usage with a colour code (green under 50%, yellow 50–75%, red above 75%), session cost in USD, session duration, Git diff stats.
Cost comes from a cached copy of Anthropic's pricing, refreshed daily by a small script that scrapes the public pricing page and writes per-model rates to ~/.claude/cache/:
if [ ! -f "$PRICING_CACHE" ] || \
[ "$(( $(date +%s) - $(stat -f %m "$PRICING_CACHE") ))" -gt 86400 ]; then
bash "$PRICING_SCRIPT" &>/dev/null &
fiNo API key involved. The colour-coded context bar is what I actually watch — it tells me when to /compact before the window fills up.
Rules and CLAUDE.md are static: you write them, Claude reads them. Auto memory is dynamic, and it's a built-in feature as of Claude Code v2.1.59. Claude takes its own notes during a session — build commands it figured out, corrections you made, architecture patterns it learned — and saves them to ~/.claude/projects/<project>/memory/ as plain markdown files.
The directory contains a MEMORY.md entrypoint plus any number of topic files Claude creates as needed:
~/.claude/projects/<project>/memory/
├── MEMORY.md # Index, loaded into every session
├── user-profile.md # Role, preferences, expertise
├── feedback.md # Corrections and confirmed approaches
├── architecture.md # Decisions worth remembering
└── references.md # Pointers to external systems
At session start, the first 200 lines (or 25 KB) of MEMORY.md get loaded automatically. Everything else loads on demand when Claude decides it needs the detail. The whole thing is plain markdown — you can read, edit, or delete entries directly, or open the directory via /memory inside a session.
My additions on top of the built-in behaviour:
name, description, type) so the MEMORY.md index stays scannable and future Claude knows what each file is for.user (identity, preferences), feedback (corrections), project (architecture decisions), reference (pointers to external systems). This is just a convention encoded in my global CLAUDE.md, but it keeps the directory organised instead of turning into a pile.CLAUDE.md that says "when you learn something worth keeping, write it to auto memory" — because the default behaviour is conservative.In practice this means Claude already knows I'm a native Chinese speaker who prefers terse replies, that my Efision project uses a 5-layer Clean Architecture with Service<R> generics, and that running npm inside a pnpm project is a mistake I've corrected before. These details compound. Over months they change how Claude approaches every task.
The one bit of discipline that makes memory work: don't try to front-load it. Let it build from real corrections. A memory written because the model actually got something wrong is load-bearing; one written preemptively is noise.
Skills are Claude Code's unit for reusable workflows. Each one is a directory under ~/.claude/skills/ (or a project's .claude/skills/) containing a SKILL.md file and any supporting assets. The frontmatter tells Claude when to use it, and the markdown body tells it how.
A minimal skill:
---
name: deploy
description: Deploy the application to production
disable-model-invocation: true
allowed-tools: Bash(gh *) Bash(vercel *)
---
Deploy $ARGUMENTS to production:
1. Run the test suite
2. Build the application
3. Push to the deployment target
4. Verify the deployment succeededA few frontmatter fields I use often:
disable-model-invocation: true — "only the user can call this". Use for workflows with side effects, so Claude never auto-triggers a deploy because the code looks done.allowed-tools — grants the skill permission to run specific tools without prompting while it's active.context: fork with agent: Explore — runs the skill in a subagent's isolated context, good for research tasks that shouldn't pollute the main conversation.The one I use most is /ship. One command, working tree to merged PR:
gh pr checks every 30 seconds (10-minute timeout); on failure, read logs, fix, push, retry up to two times.No scripting. Claude just follows the markdown and stops to ask if something breaks. It replaces what used to be five to ten manual commands.
I also have a deep-research skill — eight steps that turn a vague question into a structured report, with sources tiered by authority and intermediate artifacts saved to ~/Downloads/research/<topic>/. It's the skill equivalent of a lab notebook.
Plugin skills live inside plugins and are invoked with the namespaced form: /interface-design:audit, /commit-commands:commit-push-pr. Your own skills under ~/.claude/skills/ use the plain form: /ship, /deep-research.
With everything above in place, a day looks like this:
CLAUDE.md, project CLAUDE.md, rules, auto memory, and plugin skills all load automatically./compact at a natural breakpoint. Root-level rules and CLAUDE.md re-inject from disk automatically.Nothing about this feels like operating an AI. It feels like pair programming with someone who has already read the code, already knows my preferences, and has opinions about which tool to use.
If you're starting from nothing and want the biggest return per hour of setup, in order:
rm -rf, --force, publish, curl | bash before you ever need them blocked.CLAUDE.md. Keep it short — under 200 lines. Identity, working style, a few behavioural rules..claude/rules/. Anything you've typed into a chat twice deserves a rule file. Use paths frontmatter to scope rules that only apply to certain parts of the tree.PostToolUse linter reminder. One-liner, catches real issues./plugin install github@claude-plugins-official alone pays for itself in a week.Most of my configuration came from real mistakes — a force-push that shouldn't have happened, an npm install in a pnpm project, a session that ran out of context mid-implementation. Every rule in the file exists because I once needed it to.
Every link below is a page I consulted while writing this post. The notes are what you'll actually get from each — not just the page title.
settings.json. Start here if you're setting things up from scratch.CLAUDE.md loading order, .claude/rules/ with paths frontmatter, and how the built-in auto memory directory is stored and re-loaded.command / http / prompt / agent handler types. Essential if you want hooks that do something rather than just print reminders.$ARGUMENTS substitution, inline shell-exec blocks, and context: fork for running a skill inside a subagent. Read this before writing anything beyond a trivial /command./plugin install <name>@<marketplace> flow. The second link is the one that actually shows you how extraKnownMarketplaces works.