Claude Academy
Sign in

Vault / course/projects/p11-custom-command-and-hook.md

updated 2026-06-25

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

  1. Write a custom command — Create .claude/commands/review-fn.md. The body is the prompt; $ARGUMENTS is 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 $ARGUMENTS arrived as parseInvoice.

  2. 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.

  3. Verify the hook fires — Have Claude edit a deliberately mis-formatted file. Confirm the formatter ran automatically and the diff came back clean.

  4. 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.

  5. Commit — Add .claude/commands/review-fn.md and .claude/settings.json so the whole team inherits the command and the hook.

Acceptance criteria — you're done when

  • /review-fn <name> runs and $ARGUMENTS is correctly substituted into the prompt.
  • A PostToolUse hook auto-runs your formatter/linter after Edit/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.json are committed (not in settings.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 Stop hook 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: deny Bash(git push:*), allow Bash(gh:*).

Self-assessment rubric

LevelSignal
🟢 Got itYou configure Claude Code reflexively — commands, hooks, and plan mode are part of how you work, not a novelty.
🟡 AlmostThe command and hook work, but you still skip plan mode on tasks that clearly need it.
🔴 RevisitHook didn't fire or $ARGUMENTS confused you. Re-watch claude-code-in-action and re-read claude-code-settings.

See also