The previous post pushed the Claude Code internals off to a follow-up. This is that follow-up.
The first time you open Claude Code to customize it, the surface feels scattered. settings.json, CLAUDE.md, slash commands, subagents, hooks, plugins — the same intent has too many possible homes, and the question “where does this go?” blocks you before anything else. Dump everything into CLAUDE.md and it bloats; split it and you lose track of which file fires when.
Pick one axis and the problem almost dissolves. When does it step in? This post walks through Claude Code’s customization surface rearranged along that axis — into four layers.
Layer Structure
| Layer | When it steps in | Responsibility |
|---|---|---|
| CLAUDE.md + Rules | Always (loaded into every turn) | Tacit conventions and guardrails |
| Agents | When a Skill or model delegates to it | Context-isolated specialist roles |
| Skills | When I call it | Reusable workflow recipes |
| Hooks | Automatically, before/after tool use | Validation, automation, safety rails |
That table is arguably the whole post. The rest is how each layer settles onto this axis, and how all four interlock inside a single workflow.
CLAUDE.md + Rules
The knowledge Claude loads into context every turn. It applies even when nothing is called. Two tiers.
CLAUDE.md is the top-level context at the project/user level. It can live at ./CLAUDE.md, .claude/CLAUDE.md, or ~/.claude/CLAUDE.md — when multiple exist, they merge in hierarchy order. My CLAUDE.md holds language-independent behavioral rules.
# CLAUDE.md (excerpt)
- On rejected approach: stop immediately and ask for direction
- Change scope: only change what was explicitly requested
- No commits: never commit until explicitly asked
- Propose approach first: for changes touching 3+ files or affecting
architecture, propose the approach before writing code
rules/*.md is the lower tier, split per language and domain. Drop .md files into ~/.claude/rules/ or .claude/rules/ and they’re discovered recursively. A paths frontmatter field enables scoped rules that only apply to matching file patterns.
# rules/go.md (excerpt)
- No Get prefix: GetName() ❌ → Name() ✅
- Error wrapping: bare return err forbidden. fmt.Errorf("context: %w", err)
- No panic in libraries: only in main/tests
# rules/typescript.md (excerpt)
- Destructuring-first: function params ({ server, db }: Config)
- Braces required: if (x) return; ❌ → if (x) { return; } ✅
- Data shapes → type, implementation contracts → interface
- Use enum: prefer enum over as const objects
# rules/code-principles.md (excerpt, language-agnostic)
- fail-fast: raise/throw immediately on validation failure
- Guard clauses: early return instead of nested if
- Immutability-first: limit mutations to explicit scopes
- Pure functions preferred: push side effects to call boundaries
- No Any types: use generics, unions, concrete types
Splitting language rules into rules/ instead of putting them in CLAUDE.md keeps CLAUDE.md from swelling.
The test: “does this need to be in effect regardless of whether anything is called?” If yes, it belongs in this layer.
Agents
Agents are invocation units, but they don’t run directly in the main session. They’re defined at ~/.claude/agents/<name>.md and are delegated to by a Skill (covered next) or by the model itself. The key word is context isolation. An Agent opens its own context window, drills into a single responsibility, and hands back only the result.
Take the architect agent — it handles architecture analysis and root-cause debugging. When some skill dispatches architect, the agent reviews the design in a separate context and returns a conclusion. Everything the agent processed stays out of the main session. Or verify-agent: it runs build → typecheck → lint → test as an isolated pipeline and reports only pass or fail. refactor-cleaner removes dead code and unused imports in isolation. code-reviewer, security-reviewer, and database-reviewer each inspect implementation output through a different lens.
The instinct for separating Skills and Agents is this. A Skill orchestrates; an Agent is the deep execution of one responsibility. Breaking work into stages and wiring the right tools into each stage is Skill work. When one of those stages needs to run independently without polluting the main context, that slot is filled by an Agent. The diagrams in the next section show exactly how these agents get dispatched.
The test: “does this need to be separated from the main context?” If not, a Skill is enough.
Skills
Skills are workflow recipes I call explicitly. Each one lives at ~/.claude/skills/<name>/SKILL.md and is triggered with /skill-name. Prompt, allowed tools, and model assignment are all bundled into a single file. Instead of retyping the same instructions every time, a recipe steps in: “for this situation, use this skill.”
/code
The natural home for Skills is repetitive development work. The heaviest skill in my setup is /code. It inspects the input and auto-detects two paths: pass it a text description and it enters design (Brainstorming); pass it a .claude/plans/ directory and it enters implementation (Pipeline).
When given a text description, the Brainstorming path runs. It explores requirements, decides whether to split the work, and writes a design document (DESIGN.md), per-sub-task plan files (NN-<task>.md), and a dependency graph (_dag.yaml) under .claude/plans/<topic>/. It runs the architect agent for a design review and stops. The output becomes the Pipeline path’s input.
flowchart TD
I["Idea input
/code (Brainstorming)"] --> C["Context collection
project type · CLAUDE.md · git log"]
C --> R["Requirements exploration
AskUserQuestion 1:1"]
R --> A["2-3 approaches + recommendation"]
A --> PM["pm-code-agent
split decision"]
PM -->|SINGLE| D1["DESIGN.md + _dag.yaml
01-main.md"]
PM -->|SPLIT| D2["DESIGN.md + _dag.yaml
NN-task.md × N"]
D1 --> AR["architect agent review"]
D2 --> AR
AR -->|NEEDS REVISION| D2
AR -->|APPROVED| S["status
draft → ready"]
S --> O[(".claude/plans/<topic>/")]
Passing a .claude/plans/<topic>/ directory switches to the Pipeline path. The interesting part is that the details are kept out of SKILL.md and live in references/stage-*.md, pulled in with Read only at the moment they’re needed. Normal turns only carry the orchestrator in context, and each stage document loads only when that stage begins.
Sub-tasks from _dag.yaml are topologically sorted, and each one passes through five stages.
flowchart TD
I[("/code input
.claude/plans/<topic>/")] --> L["Load _dag.yaml
topo-sort + status gate"]
L --> PRE["Stage Pre
architect agent
+ planner agent"]
PRE --> IMP["Stage Impl
parallel build
(agent team)"]
IMP --> POST["Stage Post (parallel)
code-reviewer
security-reviewer
database-reviewer
verify-agent"]
POST -->|FAIL| FIX["Stage Fix
verify-agent
auto-repair"]
FIX --> POST
POST -->|PASS| CLEAN["Stage Clean
refactor-cleaner
agent"]
CLEAN --> DONE["status
ready → done"]
DONE --> N{"Next sub-task?"}
N -->|yes| PRE
N -->|no| R["Final report"]
- Pre (
stage-pre.md) — callsarchitectandplannerin order to do structural analysis and produce the execution plan. The result is appended to the sub-task document as## Plan. - Impl (
stage-impl.md) — runs parallel implementation based on the plan. Simple work is handled directly by the leader; complex work spawns a team of agent members. - Post (
stage-post.md) — callscode-reviewer,security-reviewer,database-reviewer, andverify-agentin parallel for comprehensive review. PASS / NEEDS ATTENTION / FAIL is decided here. - Fix (
stage-fix.md, conditional) — if Post returns FAIL,verify-agentruns to auto-fix fixable errors, then Post is re-run. Bound byretry-policy.md: max 3 attempts by default, with “same error twice in a row → stall detection.” - Clean (
stage-clean.md) —refactor-cleanerremoves dead code, unused imports, and duplication. Failure here is treated as non-critical — a warning is logged and the pipeline continues. If cleanup happens to break the build, Post catches it on the next run.
When a sub-task clears all five stages, its NN-<task>.md frontmatter gets promoted from status: ready to status: done. That transition is the re-run guard — running /code again against the same plan skips any task already marked done and only picks up the rest.
This skill shows the basic pattern of the Skill layer. Auto-detecting paths based on input, passing stateful files between paths, and offloading details from SKILL.md to references to keep context lean.
/github-ship — Branch to Merge in One Pipeline
Once implementation is done, the code needs to land. /github-ship bundles everything from branch creation to merge into a single pipeline. It’s the consolidation of what used to be three separate skills: /git-branch, /git-commit, and /github-pr-push.
It runs five phases.
- Branch — analyzes the changes by concern, decides whether to split into multiple PRs, and creates a convention-aligned branch.
- Commit — reviews the staged diff, splits by concern, and writes convention-conformant messages.
- Push & PR — runs static analysis (lint/typecheck), pushes, and creates a PR via
gh pr create. - Review — scales review intensity by change size (TRIVIAL/SMALL/MEDIUM/LARGE), fires review agents in parallel. If issues are found: fix → push → re-review loop.
- Merge — once all review issues are resolved, offers squash or merge commit, then merges.
When /code finishes with all sub-tasks passing, it automatically asks whether to run /github-ship. Approve, and implementation flows seamlessly into PR merge.
Two PR-related skills remain standalone.
/github-pr-review <PR number>— deep-reviews an existing PR. Uses the same agents asgithub-shipPhase 4, but callable independently./github-pr-respond— walks through review comments on a PR, confirming whether to address each, and posts replies.
CLI over MCP
There are two main ways to wire tools into Claude Code: MCP servers and CLI invocation. In the git/GitHub space both options exist. Yet all the skills above call CLIs like gh and git via Bash(...:*) in their allowed-tools. The reason is context savings.
The moment an MCP server connects, its entire tool catalog becomes resident in context. A few hundred tokens per tool, and a single server exposing around twenty tools consumes thousands of tokens “doing nothing.” Attach three such servers and 4,000+ tokens are gone before you type a single character (Scott Spence). CLI tools only spend tokens when invoked. In real comparisons, CLI reduced token usage by roughly 68% against MCP on the same workload (BSWEN — MCP vs CLI), and monthly operating costs came out 4 to 32× apart in another sample (BSWEN — Token usage).
Anthropic is aware of this cost and has introduced lazy-loading optimizations like Tool Search, which reportedly cut overall agent tokens by 46.9% when MCP is in use (Joe Njenga, Medium). Even so, in domains where mature CLIs already exist — git and GitHub are the obvious ones — the skill + Bash combo is still the lightest option. That’s why github-ship and the other git/GitHub skills in my setup don’t touch MCP and route everything through the CLI.
The test: the inner axis of the Skill layer is “do I call it, or can the model call it on its own?” The disable-model-invocation flag draws that line — write operations that are risky or hard to reverse stay locked, while anything that needs to auto-trigger for everyday productivity stays open.
Hooks
Hooks are never invoked. They react to tool events and run automatically. Register them in settings.json under hooks as PreToolUse / PostToolUse, and shell scripts step in before or after specific tool calls.
Hooks cover two slots.
- Safety rails — block dangerous commands before they execute.
remote-command-guard.shhooks in before a Bash call (PreToolUse), checks categories likerm -rf,curl | sh, and reads of/etc/passwd, and blocks withexit 2if anything matches. - Automation — running formatters after an edit (
format-file.sh), nudging for security review when a sensitive file is touched (security-auto-trigger.sh), masking secrets in every tool’s output (output-secret-filter.sh). The “things you don’t want to do by hand but must do every time” slot.
Permission allow/deny pairs with Hooks. The permissions.deny list in settings.json is a static filter. Declare a pattern like Bash(*rm -rf*) and matching commands never reach the tool call at all. Hooks layer dynamic checks on top. The context-dependent risks that static filters miss (specific redirect targets, conditional combinations) get judged by the script. Static declarations + dynamic inspection, the two tiers bundled into one layer that plays the role of safety rail.
The test: “does this need to intervene automatically on a tool event?” The no-invocation requirement is what separates Hooks from Skills and Agents.
Integrated Workflow
Taken one at a time, each layer’s responsibility stays sharp. But in practice all four run inside the same workflow. Here’s one flow as an example.
- I type
/code "design a new auth module"→ a Skill starts moving on the Brainstorming path. - The Skill dispatches
architectandplannerinternally → Agents perform design analysis and step decomposition inside isolated contexts. - Throughout all of this, every turn has language-specific rules and code principles loaded into context → Rules are quietly in effect.
- Once design is done,
/codeasks whether to switch to Pipeline. Approve, and it starts writing files →Edit/Writefires over and over. - Each time, Hooks react.
format-file.shruns the formatter,code-quality-reminder.shnudges error handling and immutability checks, and for security-related filessecurity-auto-trigger.shrequests review. - At the end of the pipeline,
/codecallsverify-agentagain → build, typecheck, lint, and tests run in isolation and only the result comes back. - If everything passes,
/codeasks whether to run/github-ship→ approve, and another Skill takes over: branch → commit → push → review → merge in one shot.
All four layers participate in a single task, and none of their responsibilities overlap. What I called (Skills), what the Skill delegated (Agents), what was always in place (Rules), what reacted to an event (Hooks) — none of these four overlap.
Summary
When deciding where a new piece of configuration goes, four questions are enough.
- Must it always be in effect? → CLAUDE.md + Rules
- Must it react automatically to a tool event? → Hooks
- Is it a workflow that runs when invoked? → Skills
- Must it run in a separate context? → Agents
If more than one applies, split the responsibility. Full config reference: .dotfiles/claude/.