⏱ ~8 min read. 📊 Token cost tables
I completed Anthropic’s Introduction to Agent Skills certificate. Like most certifications, it gave me the vocabulary. What it didn’t give me was the judgment — when to use which tool, what it actually costs, what to build first in a real codebase.
This article is what I wish I’d had before I started.
You start using Claude Code. It’s fast. Then one day it edits .env when you didn't mean it to. Or it reads 15 files just to answer a question about your repo structure. Or a PR review floods your context so badly that Claude loses track of what it was doing.
These aren’t bugs. They’re the cost of not knowing the four extension points Claude Code gives you:
Each solves a different problem. Most people discover them in the wrong order. Let’s fix that.
Every feature plugs into one of those slots. Now let’s build from the highest ROI down.
Skills are Markdown files Claude uses as tools. The insight that makes them valuable: Claude only sees the description at session start (~100 tokens). The full body loads only when a task matches.
A skill with 5,000 tokens of reference material costs almost nothing until you need it.
The highest-value skill for any project is a codebase overview. Without it, Claude reads 10–15 files every session just to understand where things live.
Setup — run once:
mkdir -p .claude/skills/codebase-overviewmkdir -p .claude/skills/summarize-changes
.claude/skills/codebase-overview/SKILL.md
---description: Load when asked about repo structure, architecture, where to find things, or naming conventions.---## Monorepo layoutpackages/api — Express REST API, all routes and servicespackages/web — React customer-facing apppackages/admin — React internal dashboardpackages/shared — shared types, utils, validation schemas## Commands- `pnpm test` — run all tests- `pnpm lint` — lint check- `pnpm build` — build all packages## Conventions- Components: PascalCase.tsx- Custom hooks: useCamelCase.ts, live in hooks/- Tests: co-located *.test.ts- API routes: kebab-case, versioned under /api/v1/
Test it: Ask Claude “where do I add a new hook in this repo?” — it answers without reading a single file. Run /usage before and after to see the token difference.
The description field is what Claude matches on. Make it task-oriented: "Load when asked about..." — not a noun like "Codebase info". That one detail is the difference between a skill that auto-invokes and one that sits unused.
Skill locations:
.claude/skills/<name>/SKILL.md ← project-level, commit this~/.claude/skills/<name>/SKILL.md ← personal, all your projects
No restart needed — Claude Code features native skill hot-re.
Once skills give Claude context, hooks prevent Claude from doing things it shouldn’t.
“Never edit .env” in CLAUDE.md is a request. Claude can still do it.
A PreToolUse hook blocking that edit is enforcement. It cannot be bypassed.
Exit codes: exit 0 = proceed. exit 2 = block the action. Stderr output appears as feedback to Claude in the UI.
Two files, both committed to your repo:
.claude/hooks/guard-files.shFILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // .path // empty')case "$FILE" in .env|.env.*|*.pem|*.key) echo "Blocked: protected file $FILE" >&2 exit 2 ;;esac
.claude/settings.json{ "hooks": { "PreToolUse": [{ "matcher": "Edit|Write", "hooks": [{"type": "command", "command": "bash .claude/hooks/guard-files.sh"}] }] }}
Test it: Ask Claude “add TEST=1 to my .env file” — it blocks. Claude sees the stderr message as feedback explaining why. Commit both files — every developer on the team gets the protection automatically.
.claude/hooks/format-ts.shFILE=$(echo "$CLAUDE_TOOL_INPUT" | jq -r '.file_path // empty')if [[ "$FILE" == *.ts || "$FILE" == *.tsx ]]; then npx prettier --write "$FILE" 2>/dev/nullfi
Add to .claude/settings.json (merge into existing hooks object):{ "hooks": { "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [{"type": "command", "command": "bash .claude/hooks/format-ts.sh"}] }] }}
Test it: Ask Claude to edit any .ts file. Check it after — Prettier already ran.
Files that always need protection: .env / .env.* (secrets), *.pem / *.key (private keys), lock files pnpm-lock.yaml / yarn.lock / package-lock.json (dependency integrity)
You’ve hit this: ask Claude to review a PR and suddenly your context is full of lint output, file contents, and intermediate reasoning. That’s the problem sub-agents solve.
Sub-agents are isolated Claude instances. Each gets its own context window. They do the verbose work — and return only a summary. Your main conversation stays clean. Up to 10 run in parallel.
.claude/agents/code-reviewer.md---name: code-reviewerdescription: Review code changes for quality, unused imports, missing error handling, type safety. Invoke after editing files or before committing.model: claude-sonnet-4-20250514tools: [Read, Glob, Grep, Bash]skills: [codebase-overview]---When invoked:1. Run `git diff --name-only HEAD` to find changed files2. Read each changed file3. Check for: unused imports, missing error handling, unsafe type casts4. Run `pnpm lint 2>&1 | grep -E "error|warning" | head -20` Return exactly:## Issues found## Lint output## Verdict (approve / needs changes)Keep response under 300 words.
Test it: Edit any TypeScript file. Say “use code-reviewer to check my changes”. Only the structured summary comes back to your main conversation.
Two fields you must always declare explicitly:
tools: — omitting this grants ALL tools including connected MCP servers. List only what the sub-agent needs.
skills: — omitting this means no skills load. Sub-agents don't inherit from the parent. If it needs to understand the repo, skills: [codebase-overview] must be explicit.
Model routing for cost:
claude-haiku-4-5-20251001 ← simple tasks: search, grep, summarizeclaude-sonnet-4-20250514 ← most work: review, write, refactoromit ← inherits from main conversation
Haiku handles most mechanical sub-agent tasks at a fraction of Sonnet’s cost. Use it.
Hard limits: Sub-agents cannot spawn other sub-agents. Cannot share data directly with siblings. Built-in Explore and Plan agents don’t have access to your custom skills.
Everything in CLAUDE.md injects into the main agent's context on every single message. ~200–500 tokens per message, not per session.
Use it for facts. Move procedures to skills — they load on demand, not every message.
CLAUDE.md (repo root):
## ProjectTypeScript monorepo: packages/api, packages/web, packages/admin, packages/shared.## Commands- `pnpm test` — run tests- `pnpm lint` — lint- `pnpm build` — build## Rules- Never edit .env, *.pem, *.key, or migration files- PR titles: conventional commits (feat:, fix:, chore:)- Tests are co-located (*.test.ts next to source)## Do not- Run `pnpm install` without asking- Commit directly to main
Keep it under 200 lines. If a section reads like a procedure (“when doing a release, run X, then Y…”) — move it to a skill.
Scope:
CLAUDE.md ← repo root, project-widepackages/player/CLAUDE.md ← loads when Claude navigates there~/.claude/CLAUDE.md ← personal, all your projects
Published enterprise averages (Anthropic): ~$13/active dev/day · $150–250/dev/month · 90% of users under $30/active day.
The levers that move the needle most: Haiku for simple sub-agent tasks, /clear between unrelated tasks (stale context charges every message), and keeping CLAUDE.md lean.
Week 1 — Skills first. Write codebase-overview. Spend time on the description field. Test that it auto-invokes. Pays back immediately, compounds every session.
Week 1 — Hooks second. Add guard-files.sh + PreToolUse config, commit both. Add format-ts.sh + PostToolUse config if your team uses Prettier. Zero tokens, permanent protection.
Week 2 — Sub-agents. Write code-reviewer. The explicit skills: and tools: fields are not optional. Test it on a real PR before relying on it.
Ongoing — CLAUDE.md. Prune as the project grows. Every line that should be a skill costs tokens on every message it stays here.
Hook fires but doesn’t block: Only exit 2 blocks. exit 1 is an error, not a block.
Hook not firing at all: Run /hooks. If missing — JSON syntax error in settings.json, usually a missing comma. Matchers are case-sensitive: Edit|Write not edit|write.
Skill never auto-invokes: The description is the matching signal. Test by prompting with exact phrases from it. Run /skills to confirm it loaded.
Sub-agent doesn’t know the codebase: Doesn’t inherit skills. Add skills: [codebase-overview] to frontmatter. Name must match exactly.
Token costs higher than expected: Run /usage. Move procedures from CLAUDE.md to skills. Use /clear between tasks. Route simple sub-agents to Haiku.
Skill edits not taking effect: No restart needed for edits. New top-level skills directory mid-session requires one restart.
/usage — token breakdown by feature. Run before and after changes to confirm optimizations work.
/clear — reset context between unrelated tasks. Stale context is silent token waste.
/hooks — browse active hooks. Fastest way to debug a hook not firing.
/skills — list loaded skills for this session.
/context — see what's consuming context window space right now.
/compact — summarize conversation to free up context without losing the thread.
The hardest part of this system isn’t the setup — it’s the judgment. Knowing a PreToolUse hook gives real enforcement while a CLAUDE.md rule gives a suggestion. Knowing a sub-agent's tools: field isn't optional. Knowing the description field in a skill is load-bearing.
Those things only become clear when something doesn’t work the way you expected.
This is the article I’d have wanted before my first broken hook and my first sub-agent that knew nothing about the repo it was reviewing.
Build the codebase-overview skill first. Everything else follows.
All examples verified against Anthropic’s Claude Code documentation (code.claude.com/docs). Token figures are Anthropic-published estimates. TypeScript/pnpm examples — adapt to your stack.
I Spent a Week Learning Claude Code’s Agent System. Here’s What Actually Matters. was originally published in Towards AI on Medium, where people are continuing the conversation by highlighting and responding to this story.