Agent Code Academy
Home/Phase 3: Mastery
Week 9 of 12

Skills, Hooks & Custom Commands

Teaching Claude YOUR workflow

Objective

Deep dive into Claude Code's extensibility system.

Deliverable

At least 2 custom skills (one forked, one inline), 3 hooks (command, prompt, agent types), and 1 custom command.

Topics

  • Skills: SKILL.md files for project patterns
  • Skill frontmatter: name, description, allowed-tools, context, agent, model, hooks
  • How skills auto-invoke based on context matching
  • Custom slash commands via .claude/commands/
  • Hooks: 14 events covering the full Claude Code lifecycle
  • Three hook types: command, prompt, agent
  • Hook decision control: exit codes, JSON decision patterns
  • Async hooks for non-blocking background execution

Activities

  • Create a custom skill for your project with supporting files
  • Create a skill with `context: fork` that runs in an Explore subagent
  • Write a `PreToolUse` command hook that validates Bash commands before execution
  • Write a `Stop` prompt hook that checks if all tasks are complete before Claude stops
  • Create a custom /deploy command with `disable-model-invocation: true`
  • Test skill auto-invocation and argument passing
  • Build an async `PostToolUse` hook that runs tests in the background after file edits
  • Use dynamic context injection in a PR summary skill

Skills You'll Gain

Skills system, hooks (all 3 types), custom commands, workflow automation, skill forking


Learning Objectives

By the end of this week, you will be able to:

  1. Create custom skills with SKILL.md files that automate repetitive project workflows
  2. Explain the three hook types (command, prompt, agent) and when to use each
  3. Write hooks that validate, log, and control Claude Code's behavior
  4. Build a custom slash command for common project operations
  5. Use advanced skill features like forked context and dynamic injection

Lesson

Why Extensibility Matters

So far, you have used Claude Code's built-in features. But every project and every developer has unique workflows. Maybe you always want tests to run after editing a file. Maybe you need Claude to follow specific coding standards. Maybe you want a one-command deploy workflow.

Skills, hooks, and custom commands let you teach Claude Code your specific workflow — turning it from a general-purpose assistant into a custom tool built for your project.

Skills: Teaching Claude Project Patterns

A skill is a SKILL.md file that tells Claude how to handle a specific type of task. Skills live in your project's .claude/skills/ directory.

Think of skills as recipe cards for Claude. Instead of explaining your deployment process every time, you write a skill once and Claude follows it automatically.

Basic skill example (.claude/skills/deploy.md):

---
name: deploy
description: Deploy the application to production
allowed-tools: ["Bash", "Read"]
---

# Deploy Workflow

1. Run `npm run build` and verify no errors
2. Run `npm test` and verify all tests pass
3. Run `npx vercel deploy --prod`
4. Verify the deployment URL is accessible

Now you can type /deploy and Claude follows these exact steps.

Skill frontmatter (the section between --- marks) controls behavior:

FieldWhat It Does
nameThe slash command name (e.g., /deploy)
descriptionWhat the skill does (shown in /help)
allowed-toolsWhich tools Claude can use (limits scope)
context: forkRun in a separate subagent (isolated context)
modelWhich AI model to use for this skill
disable-model-invocationIf true, Claude cannot trigger this skill itself — only you can
user-invocableIf false, only Claude can trigger it (not you)

Auto-invocation: Skills can match context automatically. If you describe a skill well, Claude recognizes when to use it without you typing the slash command. For example, a skill named "code-review" with description "Review code for quality and bugs" might activate when you say "review my code."

Dynamic context injection: Use backtick-wrapped shell commands to inject live data into skills:

# PR Summary Skill

Current PR diff:
!`gh pr diff`

Summarize the changes above.

The !gh pr diff`` runs before Claude sees the skill, replacing itself with the actual output.

Hooks: Automating Claude's Lifecycle

Hooks are event handlers — code that runs automatically when specific things happen in Claude Code. Think of them as tripwires: when Claude is about to do something (or just did something), your hook fires.

The 14 hook events:

EventWhen It FiresCan Block?
SessionStartSession begins or resumesNo
UserPromptSubmitYou submit a promptYes
PreToolUseBefore a tool call executesYes
PermissionRequestWhen a permission dialog appearsYes
PostToolUseAfter a tool call succeedsNo
PostToolUseFailureAfter a tool call failsNo
NotificationWhen Claude sends a notificationNo
SubagentStartWhen a subagent is spawnedNo
SubagentStopWhen a subagent finishesYes
StopWhen Claude finishes respondingYes
TeammateIdleWhen a teammate is about to go idleYes
TaskCompletedWhen a task is marked completedYes
PreCompactBefore context compactionNo
SessionEndWhen a session terminatesNo

Three Hook Types

TypeHow It WorksUse When
commandRuns a shell script. Exit 0 = proceed, exit 2 = block.Deterministic rules (linting, logging, validation)
promptSends a prompt to a Claude model for yes/no judgment.Decisions requiring judgment
agentSpawns a subagent that can read files and run tools (up to 50 turns).Verification requiring codebase inspection

Command hook example — Block dangerous Bash commands:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "jq -r '.tool_input.command' | grep -q 'rm -rf' && exit 2 || exit 0"
      }]
    }]
  }
}

This hook fires before every Bash command. It checks if the command contains rm -rf. If yes, it blocks (exit 2). If no, it proceeds (exit 0).

Prompt hook example — Check task completeness before stopping:

{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "prompt",
        "prompt": "Check if all tasks are complete. If not, respond with {\"ok\": false, \"reason\": \"what remains\"}."
      }]
    }]
  }
}

Agent hook example — Verify tests pass before stopping:

{
  "hooks": {
    "Stop": [{
      "hooks": [{
        "type": "agent",
        "prompt": "Run the test suite and verify all tests pass. $ARGUMENTS",
        "timeout": 120
      }]
    }]
  }
}

Advanced Hook Features:

  • Async hooks: Set "async": true to run in the background without blocking Claude
  • Matchers: Regex patterns that filter when hooks fire (e.g., Bash, Edit|Write)
  • /hooks menu: Type /hooks to interactively manage hooks
  • Stop hook loop prevention: Check stop_hook_active field to prevent infinite loops

Custom Slash Commands

For simpler automation, create custom commands in .claude/commands/:

<!-- .claude/commands/test-all.md -->
Run the full test suite with `npm test`. If any tests fail,
analyze the failures and suggest fixes. Show a summary of
pass/fail counts at the end.

Now typing /test-all runs this command. Arguments are available via $ARGUMENTS, $0, $1, etc.

Practice Exercises

Exercise 1 (Guided): Create Your First Skill

  1. Create the directory: mkdir -p .claude/skills
  2. Create .claude/skills/deploy.md with:
---
name: deploy
description: Build, test, and deploy to Vercel
allowed-tools: ["Bash", "Read"]
disable-model-invocation: true
---

# Deploy to Production

Follow these steps exactly:
1. Run `npm run build` — stop if there are errors
2. Run `npm test` — stop if any tests fail
3. Run `npx vercel deploy --prod`
4. Report the deployment URL
  1. Test it by typing /deploy in a Claude Code session

Verification: Typing /deploy triggers the full build-test-deploy workflow. The deployment URL is reported at the end.

Exercise 2 (Independent): Write Three Hooks

Goal: Create one hook of each type:

  1. Command hook — Log every Bash command to a file (echo "$command" >> .claude/command-log.txt)
  2. Prompt hook — Before stopping, ask Claude to verify the code is properly formatted
  3. Agent hook — After file edits, verify the project still builds successfully

Add these to .claude/settings.json.

Hints:

  • Start with the command hook (simplest)
  • Use /hooks to verify they are registered
  • Test each hook by triggering the relevant event

Verification: After a session, .claude/command-log.txt contains logged commands. The prompt hook prevents stopping if code is not formatted. The agent hook catches build errors.

Exercise 3 (Challenge): Advanced Skill with Forked Context

Create a skill that:

  • Uses context: fork to run in an isolated subagent
  • Injects dynamic context with !command`` syntax
  • Has supporting files (a template or example file)
  • Accepts arguments via $ARGUMENTS

Example: A "pr-summary" skill that generates a PR description from the current Git diff.

Self-Assessment Quiz

1. What is a skill in Claude Code, and how is it different from a custom command?

2. Name the three hook types and when you would use each one.

3. What does exit 2 mean in a command hook?

4. What does context: fork do in a skill's frontmatter?

5. How do you prevent a Stop hook from creating an infinite loop?

Answers:

  1. A skill is a SKILL.md file with frontmatter that defines a reusable workflow. It can auto-invoke based on context, control which tools are available, and run in forked subagents. A custom command is a simpler .md file in .claude/commands/ that defines a prompt template. Skills are more powerful — they supersede custom commands.

  2. Command hooks run a shell script for deterministic rules (logging, validation). Prompt hooks send a prompt to a Claude model for judgment-based decisions. Agent hooks spawn a subagent that can read files and run tools for complex verification.

  3. Exit code 2 in a command hook means "block this action." Exit 0 means "allow it to proceed."

  4. context: fork runs the skill in an isolated subagent with its own context window, keeping the main conversation context clean.

  5. Check the stop_hook_active field in the event data. If it is true, exit 0 immediately — this means the Stop hook already fired and you should let Claude stop to avoid an infinite loop.

Skills, Hooks & Commands Updates (Feb 2026)

  • Custom command argument shorthand: Use $0, $1, etc. to access individual arguments in custom commands (v2.1.19)
  • CLAUDE_CODE_ENABLE_TASKS env var: Set to false to revert to the old task system temporarily (v2.1.19)
  • TeammateIdle and TaskCompleted hook events added for multi-agent workflows (v2.1.33)
  • Bash permission matching fixed for commands using environment variable wrappers (v2.1.38)
  • Sandbox bypass fix: Commands excluded from sandboxing via sandbox.excludedCommands or dangerouslyDisableSandbox no longer bypass the Bash ask permission rule when autoAllowBashIfSandboxed is enabled (v2.1.34)

Non-Interactive Mode & CI Automation

  • Structured outputs in non-interactive (-p) mode are now fully supported (v2.1.22). Use -p with --output-format json to get structured responses for scripts and CI pipelines.
  • Startup performance improved when resuming sessions with saved_hook_context (v2.1.29).
  • Gateway compatibility: Users on Bedrock or Vertex can set CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1 to avoid beta header validation errors (v2.1.25, v2.1.27).

Exercise:

  • Run claude -p "list all files" --output-format json and parse the structured output in a bash script
  • Set up a CI step that uses Claude Code in non-interactive mode

Auto Memory (Feb 2026)

As of v2.1.32, Claude Code automatically records and recalls memories as it works. This is a significant change to how context persists across sessions.

How it works:

  • Claude maintains a persistent memory directory at ~/.claude/projects/<project>/memory/
  • MEMORY.md is loaded into the system prompt each session (keep it under 200 lines)
  • Separate topic files (e.g., debugging.md, patterns.md) can hold detailed notes
  • Memories persist across conversations automatically

What Claude saves:

  • Stable patterns confirmed across multiple interactions
  • Key architectural decisions and important file paths
  • User preferences for workflow and tools
  • Solutions to recurring problems

What Claude does NOT save:

  • Session-specific or temporary state
  • Unverified conclusions from a single file read
  • Anything duplicating CLAUDE.md instructions

Exercise: After a few sessions on a project, inspect ~/.claude/projects/ to see what Claude has remembered. Try asking Claude to "remember" a preference (e.g., "always use bun instead of npm") and verify it persists in the next session.