Claude Code in action
Context control, custom commands, hooks, headless runs, GitHub integration and plugins.
Series Claude Academy: Learn the products Part 1 of 7
A walkthrough of the Claude Academy course Claude Code in action (Learn the products track, 9 lessons + quiz, about 1 hour). Each lesson is summarised with my own tables and examples; take the course for the full lessons. All the courses are listed in the Claude Academy course index.
Note: Built from the public lesson text. The
settings.jsonhook wiring, the example hook script, and the plugin layout tree were added from the Claude Code docs and should be checked against the lessons and current docs.
Prerequisites / Audience
- Developers who already use Claude Code for single prompts and want longer, less supervised, team-wide workflows.
- Basic Git and command-line familiarity.
Objectives (4 themes)
- Steer the work: plan mode, directed compaction, rewind,
/goaland/loop. - Configure Claude: a lean CLAUDE.md, skills, permission modes, hooks.
- Automate repeat work: routines, headless mode (
-p), Code Review and the GitHub Action. - Verify and share: verify unsupervised runs, gate on tests with hooks, package plugins.
Module 1 — Steer the work
Lesson 1: Steering long sessions
The two habits: scope the work before Claude starts, then steer it while it runs.
- Plan mode: Claude researches without editing source files and hands you a plan. Read it, don’t skim it. Iterating on a plan is much faster than cleaning up after a bad run.
- Directed compaction:
/compactsummarizes the conversation and replaces the old messages with the summary. Add instructions after the command so the summary keeps what matters:/compact Focus on the --version flag implementation - Rewind: every user prompt creates a checkpoint. Double-tap Esc on an empty prompt to open the rewind menu.
| Rewind option | Effect |
|---|---|
| Restore code and conversation | Roll back both |
| Restore conversation | Chat only |
| Restore code | Files only |
| Summarize from here | Compress everything after the checkpoint (e.g. a side conversation) |
| Summarize up to here | Compress everything before it (e.g. a long setup phase) |
- Autonomous runs:
/goal <condition>sets a completion condition. Claude keeps working across turns until a fast evaluator confirms it./goal clearcancels it. ⚠️ The evaluator reads only the transcript, so the condition must be checkable from Claude’s output (e.g. test results)./goal all tests in src/billing pass, and the type checker reports zero errors- Loop runs a prompt on an interval (fixed or self-paced), e.g. to poll CI or a deploy. Press Esc to stop a self-paced loop while it waits. A fixed-interval loop runs until you cancel it or 7 days pass.
- Worktrees for parallel sessions: each session gets its own file tree, so sessions can’t clobber each other. Clean worktrees are auto-removed when the session exits. A
.worktreeincludefile at the repo root lists git-ignored files (e.g..env) to copy into each worktree.
Module 2 — Configure Claude
Lesson 2: A CLAUDE.md that follows
- CLAUDE.md is guidance, not enforced configuration. Every line competes for attention, so the leaner the file, the more of it Claude follows.
- Hard rules belong in hooks. For example, “never push to main” should be a PreToolUse hook that blocks the action, not a CLAUDE.md line.
| Location | Purpose |
|---|---|
| Managed policy | Org-level file controlled by the platform team. Can’t be excluded |
| User | Your preferences across all projects |
| Project | Shared with the team, checked into the repo |
| Local | Git-ignored. Your personal notes for this repo (e.g. branch-specific architecture decisions) |
All four load together at launch and stack.
- Imports help organize, but they don’t reduce context: imported files are expanded inline at launch.
@.claude/conventions/code-style.md @.claude/conventions/testing.md @.claude/conventions/workflow.md - Phrasing rules that stick:
- Specific and checkable: ❌ “Follow best practices for API routes” → ✅ “Put new API routes in
src/api/handlers, one per file.” - Name the replacement: ❌ “Don’t use default exports” → ✅ “Use named exports, not default exports.”
- Emphasis is a budget: save “IMPORTANT” / “YOU MUST” for the 2–3 rules that really hurt when broken.
- Specific and checkable: ❌ “Follow best practices for API routes” → ✅ “Put new API routes in
- Keep revising it: treat each wrong behavior as a bug report against CLAUDE.md (“add that to the CLAUDE.md file”). If you can’t justify a line, delete it.
Lesson 3: Verification skills
- Build a verification skill first. It removes the dependency on you remembering to ask for checks. When its description matches (e.g. a refactor just finished), it:
- Runs the test suite
- Reads the diff
- Checks that no test was weakened to make things pass
- Reports pass/fail with evidence
- “Done” means the gates were run and observed, with results stated explicitly. “The diff looks right” is not done.
- Keep
SKILL.mdlean. Put depth inreference.md(read on demand) and tooling in scripts likecheck.sh, which are executed, not loaded into context. - Rule of thumb: if you’ve typed the same multi-step instruction twice, make it a skill.
| Surface | Owns |
|---|---|
| CLAUDE.md | Always-on conventions (naming, file locations) |
| Skill | Task-specific procedures and reference material |
| Hook | Rules Claude must not be able to skip (code that runs, not instructions) |
---
name: verify-change
description: Verifies a finished code change. Use after any refactor, feature, or bug fix before reporting done.
---
1. Run `scripts/check.sh` (do not read it) and capture the output.
2. Run `git diff` and confirm no test assertions were removed or loosened.
3. Report PASS/FAIL with the command output as evidence.
Lesson 4: Permission modes
| Mode | Behavior |
|---|---|
| Manual | Reads run without prompting. Everything else asks |
| Accept edits | Reads, file edits, and common filesystem bash run freely. You review after the fact |
| Plan | Researches and proposes without editing. With auto available, the classifier approves exploration commands |
| Auto | Accepts everything, with a separate classifier model reviewing most actions first |
| Don’t ask | Reads inside working dirs plus pre-approved tools. Anything that would prompt is auto-denied (for CI and unattended runs) |
| Bypass permissions | Skips all checks (= --dangerously-skip-permissions). Only in an isolated container or VM |
- Shift+Tab cycles manual → accept edits → plan → auto. The status bar shows the current mode.
- The auto classifier guards intent. It blocks production deploys and migrations, force-pushes, piping downloaded code into a shell, sending sensitive data to external endpoints, and irreversibly destroying pre-existing files. It allows local edits, lockfile installs, reads, and pushes to your own branch.
- ⚠️ The classifier doesn’t check correctness. Broken auth code isn’t “dangerous”, so it passes. Pair auto mode with a Stop hook that runs tests: auto guards intent before actions, and the hook guards correctness after.
Lesson 5: Hooks
- A hook is deterministic code at a fixed point in the loop. It turns “Claude usually listens” into “Claude can’t skip it”. There are about 30 events. The key ones:
| Event | Use |
|---|---|
| PreToolUse | Before a tool call. The enforcement primitive (can block or rewrite) |
| PostToolUse | After a successful call. Auto-format / lint |
| Stop / SubagentStop | When Claude (or a subagent) wants to end its turn. Refuse if not done |
| PreCompact / PostCompact | Around compaction |
| InstructionsLoaded | When CLAUDE.md or a rule file loads. Audit what reached context |
| SessionStart | Prime the environment. Use the startup source for fresh starts only |
- 💡 To re-inject context after compaction, use SessionStart with the
compactmatcher, not PostCompact. - PreToolUse JSON output (print it to stdout and exit 0).
permissionDecisionisallow|deny|ask(plusdefer, for-pruns only):{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "...", "updatedInput": { "command": "..." } } }updatedInputrewrites the call (e.g. redactsk_live_…and still run the command). It replaces the whole input object, so echo back any unchanged fields.
| Exit code | Meaning |
|---|---|
| 0 | Success. JSON on stdout is parsed. Plain text is added to context only for SessionStart / UserPromptSubmit / UserPromptExpansion |
| 2 | Blocking error. stderr is fed back to Claude (can even block Stop) |
| other (incl. 1) | Non-blocking. stderr is logged and the action proceeds ⚠️ |
- PostToolUse blocking comes too late to stop the call, but it can feed text back. Notification and SessionStart ignore blocking.
// .claude/settings.json (wiring per Claude Code docs)
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash", "hooks": [{ "type": "command", "command": ".claude/hooks/guard-bash.sh" }] }
],
"Stop": [
{ "hooks": [{ "type": "command", "command": ".claude/hooks/run-tests.sh" }] }
],
"SessionStart": [
{ "matcher": "compact", "hooks": [{ "type": "command", "command": ".claude/hooks/recap-files.sh" }] }
]
}
}
# run-tests.sh: Stop hook that refuses to end the turn on failure
out=$(npm test 2>&1) || { echo "$out" | tail -50 >&2; exit 2; }
Module 3 — Automate repeat work
Lesson 6: Routines and headless
The options form a spectrum: Routines (Anthropic infra, build nothing) → headless -p → --bare → Agent SDK (inside your app).
- Routine = prompt + repo + connectors + trigger, run in the cloud on Anthropic’s infrastructure.
- Triggers: cron schedule, HTTP POST to its API endpoint, or a GitHub event (e.g. a new PR).
- Create one at
claude.ai/code/routines, or with/schedule daily dependency audit at 9am. - Limits: it’s a research preview. Recurring runs are at most hourly. Runs start from a fresh clone of the default branch and push to a
claude/branch (PR-triggered runs start at the PR head). Use GitHub branch protection or rulesets to restrict pushes.
- Headless (non-interactive) mode:
claude -p "..."runs one-shot, reads stdin, writes stdout. It still loads hooks, skills, plugins, MCP servers, and CLAUDE.md.claude -p "Extract the exported function names from src/core/style.js" \ --output-format json \ --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \ | jq '.structured_output.functions' claude --resume "$(jq -r .session_id plan.json)" # multi-step: resume a saved session --bare: skips auto-discovery of hooks, skills, plugins, MCP, and CLAUDE.md. You get Bash, Read, and Edit plus anything passed via--settings/--mcp-config. Startup is reproducible and faster. NeedsANTHROPIC_API_KEY(no subscription login).- Agent SDK (TS/Python): a
query()function that takes a prompt plus options (allowedTools, system prompt, permission mode). You iterate over the streamed messages. - Decision guide: start with routines. Use
-pwhen the job needs your pipeline,--barefor identical CI setups, and the SDK when the work belongs inside your product.
Lesson 7: GitHub Actions and Code Review
| Code Review (managed) | GitHub Action (DIY) | |
|---|---|---|
| Hosting | Anthropic | Your GitHub runners |
| Setup | Org Owner: admin settings → Allow code review → install the Claude GitHub app → pick repos | /install-github-app (needs repo admin; sets the API key secret) |
| Triggers | PR opened / every push / @claude review comment |
PR or issue comments, cron, any GitHub event |
| Output | Inline, severity-tagged, deduped and ranked comments, plus a summary table in the check run | Commits, comments, logs |
| Limits | Never approves/blocks, never pushes fixes. Team and Enterprise plans | Whatever you configure |
- Code Review looks at the diff against the full codebase. To check a fix locally before pushing, run
/code-review(add--fixto apply its findings to your working tree). - Action inputs:
anthropic_api_key(orclaude_code_oauth_token/ workload identity federation),github_token(optional override),trigger_phrase(default@claude),use_bedrock/use_vertex/use_foundry,prompt(set it to run automatically without a mention),claude_args.
# .github/workflows/claude.yaml (core step); job needs permissions: id-token: write
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
trigger_phrase: "@claude"
claude_args: "--max-turns 5 --model claude-sonnet-5"
- Scheduled rollups: use a cron trigger plus
prompt, and read the result in the run log (a scheduled run doesn’t post a comment). Addworkflow_dispatchto allow manual runs. claude_argstuning:--max-turnscaps the loop, use a non-prompting permission mode for unattended jobs, and give minimal allowed tools (read-only for reports).
Module 4 — Verify and share
Lesson 8: Trust it — verifying unsupervised runs
- The less you watched, the more you verify.
- Run unattended work in auto mode, not bypass. The classifier still catches dangerous actions, but it never judges correctness.
- Start with the diff, not the summary. Run
/code-reviewand then readgit diffyourself. Check the files in the plan, then look for anything outside it. - Make tests a gate, not a promise: a Stop hook runs tests and refuses to end on failure, and a PostToolUse hook lints and type-checks after each edit. Exit 2 feeds failures back so Claude fixes them on its own.
- Verify headless runs by their JSON result and exit code.
- Get a cold second opinion: a fresh session or subagent with no memory of the build catches what the author rationalized away.
Lesson 9: Plugins
- A plugin is one installable unit bundling skills, subagents, hooks, MCP configs, LSP servers, monitors, themes, and a slice of settings.
/plugin install github@claude-plugins-official /plugin marketplace add your-org/claude-plugins /reload-plugins # --force if it warns about re-reading the conversation - ⚠️ Read before you install. Plugins run code with your privileges, and their hooks fire on every matching call (e.g. a Stop hook that calls a network endpoint). Being listed on a marketplace doesn’t make a plugin trusted. The official marketplace is curated separately from community submissions.
- Components run alongside yours: hooks stack. Skills, agents, and commands are namespaced (
/plugin-name:skill). A plugin’ssettings.jsonhonors only theagentand subagent status-line keys, and theagentkey promotes a plugin subagent to the main thread, changing default behavior. - Packaging your own uses the same
.claudeshape:my-plugin/ ├── .claude-plugin/plugin.json # optional manifest ├── skills/<skill>/SKILL.md ├── agents/<agent>.md ├── hooks/hooks.json └── .mcp.json{ "name": "svg-splitter-review", "version": "0.1.0", "description": "Reviews the SVG Splitter repo", "author": { "name": "Lewis Menelaws" } }nameis the only required manifest field and sets the namespace. Version the plugin like any other dependency.
Quick-Review Flashcards
- Q: How do you steer what
/compactkeeps? → A: Add instructions after it, e.g./compact Focus on the --version flag implementation. - Q: How do you open the rewind menu? → A: Double-tap Esc on an empty prompt.
- Q: What’s the key constraint on
/goalconditions? → A: The evaluator only reads the transcript, so “done” must be visible in Claude’s output. - Q: What is
.worktreeincludefor? → A: Listing git-ignored files (like.env) to copy into each worktree. - Q: Do CLAUDE.md
@importssave context? → A: No. They’re expanded inline at launch and only help organization. - Q: Where does “never push to main” belong? → A: In a PreToolUse hook, not CLAUDE.md.
- Q: What does the auto-mode classifier check? → A: Intent and danger, not correctness. Pair it with a Stop hook that runs tests.
- Q: Which exit code blocks, and which surprisingly doesn’t? → A: 2 blocks. 1 is non-blocking.
- Q: How do you re-inject context after compaction? → A: A SessionStart hook with the
compactmatcher. - Q: What are routines’ limits? → A: Research preview, at most hourly, fresh clone of the default branch, pushes to a
claude/branch. - Q: What does
--bareskip, and what does it need? → A: It skips hooks, skills, plugins, MCP, and CLAUDE.md discovery. It needsANTHROPIC_API_KEY. - Q: Can managed Code Review approve or push fixes? → A: No. It only posts findings.
Practice Ideas
- Plan a multi-file refactor in plan mode, edit the plan, then run it with
/goal "<tests pass + typecheck clean>". - Audit your CLAUDE.md: delete vague lines, rewrite rules to be checkable and to name replacements, and move hard rules to hooks.
- Write a
verify-changeskill with acheck.sh, commit it to.claude/skills, and confirm it fires after a refactor. - Add a PreToolUse Bash hook that redacts
sk_live_tokens viaupdatedInput, plus a Stop hook that exits 2 on failing tests. - Create a routine with
/schedulefor a daily dependency audit. Separately, script aclaude -p --output-format json --json-schemaextraction. - Install the GitHub Action with
/install-github-appand try an@claudecomment workflow and a cron rollup. - Package your
.claudesetup as a plugin with aplugin.jsonand install it from a private marketplace.