Claude Code: Advanced
Claude Code is now part of your daily work, and the defaults are getting in your way: too many approval prompts, the same rule re-typed every session, the same “looks done” code that misses an edge case. At this level you stop using the harness as shipped and start shaping it: permissions tuned to your trust, rules made deterministic, and checks that close the loop so you don’t have to. This is where a reliable workflow gets built.
Tune permissions, don’t go blind
Section titled “Tune permissions, don’t go blind”Shift+Tab cycles three permission modes by default: Default (asks before edits and shell), Auto-accept edits, and Plan mode. A fourth, Auto mode, joins the cycle only when your account and model qualify and you opt in.
Permission mode is a dial that trades your attention for speed. Two ways to keep the speed without the blindness:
- Allowlists. Permit known-safe commands in
.claude/settings.json(npm run lint,git commit) so Claude stops asking for the ones you would always approve. - Sandboxing. OS-level filesystem and network isolation, so Claude works freely inside a boundary instead of asking at each step.
A starting allowlist, in .claude/settings.json at your project root:
{ "permissions": { "allow": [ "Bash(npm run lint)", "Bash(npm run test *)", "Bash(git status)", "Bash(git diff *)" ], "deny": [ "Bash(git push *)", "Read(./.env)" ] }}* is a wildcard within one segment, ** spans directories, and a deny rule beats an allow for the same command. Grow the list from your own approval history, not from guesses about what you might need.
Make verification self-closing
Section titled “Make verification self-closing”The single highest-value habit at this level: stop being the verification loop yourself. Claude stops when work looks done; without a check it can read, every mistake waits for you to notice it. A check is anything that returns a pass/fail in the conversation: a test suite, a build exit code, a linter, a fixture diff, a screenshot. The escalation ladder, from least to most setup, is the core idea behind verification:
| Gate | Mechanism | Trade-off |
|---|---|---|
| In one prompt | ”Run the check and iterate until it passes.” | Least setup, most attention |
| Across a session | A /goal condition; an evaluator re-checks every turn until it holds | More setup |
| Deterministic gate | A Stop hook runs your check and blocks the turn from ending until it passes | More setup, runs unattended |
| Second opinion | A fresh subagent gets the diff and your criteria and tries to refute the result | Most setup, least attention |
Always ask for evidence, not assertions: the pasted test output, the command and what it returned. Reviewing the proof beats re-running the work yourself, and it works for runs you did not watch.
Hooks: rules that always fire
Section titled “Hooks: rules that always fire”CLAUDE.md and memory are context: advice Claude can choose to follow. A hook is a shell script that runs automatically at a lifecycle point, and it fires regardless of what the model decides.
- A
PreToolUsehook can block an action, for example refuse any write to themigrationsfolder. - A
Stophook can runnode --testand block the turn from ending until it passes. (Claude Code overrides aStophook after 8 consecutive blocks, so it cannot wedge forever.)
Configure hooks in .claude/settings.json; browse them with /hooks. A minimal Stop gate looks like this:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "./scripts/verify.sh" } ] } ] }}The script’s exit code is the contract: exit 0 lets the turn end, exit 2 blocks it and feeds stderr back to Claude as the next thing to fix.
#!/bin/shnpm test >/dev/null 2>&1 && exit 0echo "Tests are failing. Run npm test and fix them before finishing." >&2exit 2Claude can also write hooks for you: “add a hook that runs eslint after every edit to src/.” The rule of thumb: if something must happen every time, it is a hook, not a sentence in a file.
Skills and subagents
Section titled “Skills and subagents”Two ways to extend Claude without bloating the main conversation:
- Skills. A
SKILL.mdin.claude/skills/packages domain knowledge or a workflow. Claude sees the short descriptions at session start and loads the full body only when it is relevant, so a skill costs almost no context until used. For manual, side-effecting workflows, setdisable-model-invocation: trueand invoke it yourself with/skill-name, passing$ARGUMENTS. - Subagents. A
.mdfile in.claude/agents/, with its own context window, tools, and model. Hand a subagent a heavy reading job (“investigate how due dates are represented across the codebase”) and it works in a separate window, returning a summary while your main context stays clean. Configure with/agents.
MCP: reach external services
Section titled “MCP: reach external services”When a service has no good CLI, MCP connects it. claude mcp add wires up tools like Notion, Figma, databases, or monitoring. Tool definitions are deferred by default: only the names cost context until a tool is actually needed, so adding a server does not blow your window. Check per-server cost with /mcp.
Reach for a CLI you already have first (gh, aws, gcloud): a CLI costs one command and its output, far less than a service that loads many tool definitions. Use MCP when no CLI exists.
Match the tool to the goal
Section titled “Match the tool to the goal”| Goal | Reach for |
|---|---|
| Advisory knowledge | CLAUDE.md or a skill |
| Must happen every time | A hook |
| Reach an external service | A CLI, or MCP |
| Isolate heavy reading | A subagent |
You’re done when…
Section titled “You’re done when…”- ✅ You’ve added an allowlist (or sandbox) so common commands stop prompting you
- ✅ A
Stophook or/goalcondition runs your check automatically - ✅ You’ve created a skill or a subagent and know which problem each one solves
- ✅ You can name which row of the table above a new need lands on
Next step
Section titled “Next step”Ready to run the same loop wide and unattended: headless scripts, parallel sessions, agent teams, and adversarial review?