Project 11 — Custom Command & Hook
Enforces: parameterized slash commands, deterministic hooks, and plan-before-execute (from claude-code-in-action) Surface: Claude Code (terminal) · Time: ~60 min · Difficulty: 🟡 intermediate
Why this project
Out of the box, Claude Code is powerful but generic. The leverage comes from configuring it: reusable prompts you can fire with one word, deterministic hooks that enforce standards without asking, and plan mode that stops the agent from charging ahead on a fuzzy task. This project makes you build all three on a real repo.
What you'll build
Three additions committed into a repo's .claude/ directory: a custom slash command that takes $ARGUMENTS, a PostToolUse hook that auto-formats/lints after every edit, and a documented run through plan mode on a deliberately ambiguous task.
Steps
-
Write a custom command — Create
.claude/commands/review-fn.md. The body is the prompt;$ARGUMENTSis substituted with whatever the user types after the command:--- description: Review a single function for bugs, naming, and edge cases. --- Review the function `$ARGUMENTS` in this repo. Locate it, then report: 1. Correctness bugs or unhandled edge cases. 2. Naming / readability issues. 3. One concrete refactor, with a diff. Be specific; cite file:line.Run it:
/review-fn parseInvoice. Confirm$ARGUMENTSarrived asparseInvoice. -
Add a PostToolUse hook — In
.claude/settings.json, register a hook that runs after edits so formatting is never a manual step:{ "hooks": { "PostToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "npx prettier --write \"$CLAUDE_FILE_PATHS\"" } ] } ] } }Swap in your stack's formatter/linter (
ruff format,gofmt,cargo fmt). The hook fires after the tool runs — deterministic, not at Claude's discretion. -
Verify the hook fires — Have Claude edit a deliberately mis-formatted file. Confirm the formatter ran automatically and the diff came back clean.
-
Use plan mode on an ambiguous task — Give Claude something underspecified on purpose: "Make the config loading more robust." Enter plan mode (Shift+Tab to cycle, or start with
--permission-mode plan). Read the plan, push back on at least one assumption, then approve. Capture the before/after of the plan. -
Commit — Add
.claude/commands/review-fn.mdand.claude/settings.jsonso the whole team inherits the command and the hook.
Acceptance criteria — you're done when
-
/review-fn <name>runs and$ARGUMENTSis correctly substituted into the prompt. - A
PostToolUsehook auto-runs your formatter/linter afterEdit/Write, verified on a real edit. - You ran one ambiguous task through plan mode, revised the plan before approving, and saved the diff.
- The command file and
settings.jsonare committed (not insettings.local.json). - You can state, in one sentence each, when to reach for a slash command vs a hook vs plan mode.
- You journaled which of the three changed your workflow most in learning-journal-template.
Stretch goals
- Add a
Stophook that runs the test suite when Claude finishes, and have it block on failure. - Add a second command that chains two steps (e.g., generate tests, then run them).
- Scope a permission rule in
settings.json: denyBash(git push:*), allowBash(gh:*).
Self-assessment rubric
| Level | Signal |
|---|---|
| 🟢 Got it | You configure Claude Code reflexively — commands, hooks, and plan mode are part of how you work, not a novelty. |
| 🟡 Almost | The command and hook work, but you still skip plan mode on tasks that clearly need it. |
| 🔴 Revisit | Hook didn't fire or $ARGUMENTS confused you. Re-watch claude-code-in-action and re-read claude-code-settings. |
See also
- Course: claude-code-in-action
- Previous project: p10-claude-code-onboarding
- Next project: p12-build-an-mcp-server
- Deeper: claude-code-slash-commands, claude-code-settings