Course: Claude Code in Action
Mirrors: Anthropic Academy — Claude Code in Action · https://anthropic.skilljar.com/claude-code-in-action Audience: Developers, intermediate. You've done claude-code-101. · Time: ~75 min + project Prereqs: Claude Code installed, a repo you maintain, claude-code-101. · Backing notes: claude-code-plan-mode, claude-code-slash-commands, claude-code-hooks, claude-code-settings, subagents, agent-sdk Project: p11-custom-command-and-hook
101 got you running. This course turns Claude Code into a tailored workflow tool: plan mode for ambiguous work, custom slash commands for repeatable prompts, hooks for deterministic guardrails, subagents for context isolation, MCP servers for extra capabilities, and the settings.json that wires it all together — plus running Claude Code headless in CI/CD.
Learning objectives
After this course you can:
- Drive ambiguous tasks through plan mode and approve a plan before execution.
- Author a custom slash command in
.claude/commands/using$ARGUMENTS. - Add a
PostToolUsehook as a deterministic guardrail. - Delegate work to a subagent to isolate context.
- Connect an MCP server to Claude Code via
.mcp.json. - Structure
settings.jsonpermissions for a team. - Run Claude Code non-interactively in CI/CD.
Module 1 — Plan mode & custom commands
🎞 Frame 1 · Plan mode for ambiguous tasks · ⏱ ~3 min
🎬 Scene — "Migrate us off the deprecated date library." Instead of editing, Claude returns a numbered plan with files, steps, and a risk note. The user edits step 3, then approves.
🧠 Concept — Plan mode is read-only: Claude investigates and proposes a plan before changing anything. Use it whenever scope is unclear or the change spans many files — you catch a misread for the price of a read, not a redo. (Deeper: claude-code-plan-mode.)
🖼 On screen
Enter plan mode: Shift+Tab (cycle) or claude --permission-mode plan
• Write/Edit tools are blocked; Read/Grep/Fetch allowed
• Claude calls ExitPlanMode with its plan
• You: Approve → it executes · Reject → it revises
✅ Checkpoint — Name two situations where plan mode is worth the extra step, and one where it's overkill.
🎞 Frame 2 · Custom slash commands · ⏱ ~3 min
🎬 Scene — A file
.claude/commands/review.mdis created; now typing/reviewin any session runs that exact prompt.
🧠 Concept — A slash command is a reusable, parameterized prompt stored as a markdown file. Filename = command name (review.md → /review). Project commands live in .claude/commands/ (committed); personal ones in ~/.claude/commands/. (Deeper: claude-code-slash-commands.)
🖼 On screen
<!-- .claude/commands/review.md -->
---
description: Review the current diff for bugs and security.
allowed-tools: Bash(git diff:*), Bash(git status), Read, Grep
---
Review the staged and unstaged diff. Focus on correctness bugs,
security issues, and missing tests. Output a severity-tagged checklist.
✅ Checkpoint — Where do you put a slash command so your teammates get it too?
🎞 Frame 3 · $ARGUMENTS & shell interpolation · ⏱ ~3 min
🎬 Scene —
/test-for src/format.ts:formatDateruns a command whose body had$ARGUMENTSswapped in for that path.
🧠 Concept — $ARGUMENTS is replaced with whatever the user typed after the command name, making one command reusable across inputs. Backtick-wrapped shell runs and its output is embedded into the prompt.
🖼 On screen
<!-- .claude/commands/test-for.md -->
Generate and run a unit test for: $ARGUMENTS
Context — current branch: `git branch --show-current`
Changed files since main: `git diff --name-only main`
⚠️ Gotcha — Whatever you interpolate with backticks runs. Keep those commands read-only (git diff, git status) — a command file is committed and runs on every invocation.
✅ Checkpoint — What does $ARGUMENTS expand to, and what do backticks do in a command file?
Module 2 — Hooks: deterministic guardrails
🎞 Frame 4 · Why hooks, not prompts · ⏱ ~2 min
🎬 Scene — A rule written in
CLAUDE.md("always run the linter") gets skipped under load. The same rule as a hook fires every single time.
🧠 Concept — Hooks are shell commands the harness runs on lifecycle events — not the model. A prompt is advisory; a hook is guaranteed. Use hooks when "every time, no exceptions" matters. (Deeper: claude-code-hooks.)
🖼 On screen
| Event | Fires when |
|---|---|
PreToolUse | Before a tool runs (can block) |
PostToolUse | After a tool runs |
UserPromptSubmit | User submits a prompt |
Stop / SubagentStop | Agent / subagent finishes a turn |
SessionStart | New session begins |
✅ Checkpoint — Why is a hook more reliable than the same instruction in CLAUDE.md?
🎞 Frame 5 · A PostToolUse guardrail · ⏱ ~3 min
🎬 Scene —
settings.jsongains aPostToolUsehook matchingEdit|Writethat runs the formatter. Every file Claude touches is auto-formatted.
🧠 Concept — PostToolUse runs after a matching tool call — perfect for auto-format/auto-lint after edits, so the agent's output always meets your standard without you asking.
🖼 On screen
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "pnpm lint --fix" }]
}
]
}
}
✅ Checkpoint — What matcher would you use to lint only after file edits?
🎞 Frame 6 · Blocking with PreToolUse · ⏱ ~3 min
🎬 Scene — Claude tries a risky
rm. APreToolUsehook script inspects the command, exits non-zero, and the call is blocked — the reason is fed back to Claude.
🧠 Concept — PreToolUse can block: a hook that exits 2 stops the tool call and sends its stdout to Claude as the reason. This is your deterministic veto over dangerous commands — beyond a static permission, because the hook sees the actual command.
🖼 On screen
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "./scripts/guard-bash.sh" }] }
]
}
}
# guard-bash.sh — reads the call from stdin, blocks on rm -rf
grep -q 'rm -rf' && { echo "Refusing destructive rm"; exit 2; }
⚠️ Gotcha — Permissions = static yes/no on the call shape; hooks = dynamic checks on the actual command. Use permissions for blanket policy, hooks for context-dependent vetoes.
✅ Checkpoint — What exit code makes a PreToolUse hook block a tool call, and where does its stdout go?
Module 3 — Subagents & MCP
🎞 Frame 7 · Subagents for context isolation · ⏱ ~3 min
🎬 Scene — A big "audit the whole test suite" task is handed to a subagent. It reads dozens of files in its own context and returns just a summary — the main session stays clean.
🧠 Concept — A subagent runs a delegated task in an isolated context window and reports back only its conclusion. This keeps the main thread focused and stops a noisy search from flooding your context. (Deeper: subagents.)
🖼 On screen
<!-- .claude/agents/test-auditor.md -->
---
name: test-auditor
description: Audit test coverage and flaky tests. Read-only.
tools: Read, Grep, Glob, Bash(npm test:*)
---
You audit the test suite. Report gaps and flaky tests as a short list.
Do not modify files.
✅ Checkpoint — What's the main benefit of doing a big read-heavy task in a subagent?
🎞 Frame 8 · MCP servers in Claude Code · ⏱ ~3 min
🎬 Scene — A
.mcp.jsonadds a GitHub MCP server; Claude can now open PRs and read issues with new tools alongside its built-ins.
🧠 Concept — MCP servers extend Claude Code with extra tools (GitHub, a database, an internal API). Declare them in .mcp.json at the repo root (committed → the team shares them); their tools then appear alongside the built-ins, gated by the same permission model.
🖼 On screen
// .mcp.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
}
}
⚠️ Gotcha — MCP tools are still tools — they pass the permission check. Don't blanket-allow a server that can write to production.
✅ Checkpoint — In which committed file do you declare an MCP server for the whole team?
🎞 Frame 9 · settings.json & permissions · ⏱ ~3 min
🎬 Scene — Three files line up: project
.claude/settings.json(committed),.claude/settings.local.json(gitignored), and~/.claude/settings.json(personal global).
🧠 Concept — settings.json is where permissions, hooks, env, and model config live. Project settings are committed and shared; local settings are per-user and gitignored; user settings are your global defaults. First matching permission rule wins. (Deeper: claude-code-settings.)
🖼 On screen
// .claude/settings.json (committed, team-wide)
{
"permissions": {
"allow": ["Bash(npm test:*)", "Read(./src/**)", "Edit(./src/**)"],
"deny": ["Read(./.env)", "Bash(rm -rf:*)"]
}
}
✅ Checkpoint — Which settings file is gitignored and per-user, and when would you use it?
Module 4 — Claude Code in CI/CD
🎞 Frame 10 · Headless Claude Code · ⏱ ~3 min
🎬 Scene — A CI step runs
claude -pwith no human. It triages an issue, opens a PR, and exits — output captured as JSON.
🧠 Concept — claude -p "…" (print/non-interactive) runs headless in pipelines. Pair it with --output-format json for machine-readable results and --allowedTools to fence what it can do. No prompts means permissions must be pre-granted in committed settings. (Foundation: agent-sdk.)
🖼 On screen
claude -p "Triage issue #${NUM}: label it and draft a fix plan as a comment." \
--output-format json \
--allowedTools "Read,Grep,Bash(gh issue:*)"
✅ Checkpoint — Why must permissions be pre-granted in settings for a headless CI run?
🎞 Frame 11 · Guardrails for autonomous runs · ⏱ ~3 min
🎬 Scene — A CI job for autonomous PRs forces plan-then-execute, denies network writes, and a
Stophook fails the build if tests don't pass.
🧠 Concept — Autonomy raises the stakes, so stack the guardrails: plan mode (--permission-mode plan) so changes are deliberate, tight permissions (deny destructive/network-write tools), and hooks (Stop runs the test suite; PreToolUse blocks dangerous Bash). Defense in depth, because no human is watching.
🖼 On screen
Autonomous CI checklist
• --permission-mode plan (deliberate changes)
• allow: minimal tool set (least privilege)
• deny: rm -rf, prod writes (no foot-guns)
• Stop hook: run tests → fail build if red
• secret-scan hook before push
✅ Checkpoint — Name three guardrails you'd layer onto an autonomous Claude Code job.
🎞 Frame 12 · You can shape the tool now · ⏱ ~1 min
🎬 Scene — Recap: plan mode, slash commands, hooks, subagents, MCP, settings, CI — Claude Code bent to your workflow.
🧠 Concept — You can now customize Claude Code into a guardrailed, repeatable teammate. Next, go deeper on the building blocks in subagents, agent-skills, and the agent-sdk.
✅ Checkpoint — Without looking, match each to its file: slash command, subagent, MCP server, hook/permission.
🛠 Project
Complete p11-custom-command-and-hook — Ship a Command and a Hook. In a repo you maintain, you'll author one custom slash command using $ARGUMENTS, add a PostToolUse hook that auto-formats edits and a PreToolUse hook that blocks one dangerous command, commit a team settings.json permission set, and (stretch) wire a single claude -p step into CI.
🧪 Self-check quiz
- What does plan mode block, and what does it still allow?
- Where do project slash commands live, and how is the command named?
- What does
$ARGUMENTSexpand to? - Which hook event runs after a tool call? Which can block one?
- What exit code blocks a
PreToolUsecall, and where does stdout go? - What's the main reason to delegate a big read task to a subagent?
- In which committed files do you declare (a) an MCP server, (b) team permissions?
- Which flag runs Claude Code non-interactively for CI?
- Blocks Write/Edit; allows Read/Grep/Fetch. 2.
.claude/commands/; filename = command name (review.md→/review). 3. Whatever the user typed after the command name. 4.PostToolUseruns after;PreToolUsecan block. 5. Exit2; stdout is sent to Claude as the block reason. 6. Context isolation — it works in its own context and returns only a summary. 7. (a).mcp.json, (b).claude/settings.json. 8.-p/--print(non-interactive).
🎓 Certificate criteria
You've "passed" Claude Code in Action when you can:
- Drive an ambiguous task through plan mode and approve a plan.
- Author a custom slash command using
$ARGUMENTS. - Add a
PostToolUsehook and a blockingPreToolUsehook. - Delegate a task to a subagent for context isolation.
- Connect an MCP server via
.mcp.json. - Structure a team
settings.jsonpermission set. - Run one
claude -pstep in CI with fenced tools. - Complete p11-custom-command-and-hook and journal what the hook caught.
Tick this course off in progress and record the date you earned Anthropic's official Claude Code in Action certificate.
🔗 Sources & deeper notes
- Official course: https://anthropic.skilljar.com/claude-code-in-action
- Vault notes: claude-code-plan-mode, claude-code-slash-commands, claude-code-hooks, claude-code-settings, subagents, agent-sdk
- Prev course: claude-code-101 · Domain note: cca-domain-3-claude-code