Home / Development / Claude Code, part 3: Skills, subagents, hooks and plugins

Claude Code, part 3: Skills, subagents, hooks and plugins

Oct 18, 2026Noteclaude-code · skills · subagents · hooks · plugins · automation
Series Getting started with Claude Code Part 3 of 5
  1. Claude Code, part 1: Install and first session
  2. Claude Code, part 2: The everyday workflow
  3. Claude Code, part 3: Skills, subagents, hooks and plugins
  4. Claude Code, part 4: MCP, using and building servers
  5. Claude Code, part 5: RAG, giving Claude your own knowledge

Out of the box, Claude Code is a general agent. This part turns it into your agent. There are five extension points, and choosing the right one is most of the skill:

You want… Use Runs
Standing instructions in every session CLAUDE.md (part 2) Always loaded
A reusable workflow or slash command Skill When you type /name, or when Claude decides it fits
A specialist with its own context and tool limits Subagent When Claude delegates, or you ask
Something that must always happen, guaranteed Hook On lifecycle events, as a shell command
Access to an external system (DB, API, SaaS) MCP server (part 4) Tools Claude can call
All of the above, packaged to share Plugin Installed as one unit

Skills (custom slash commands)

A skill is a folder with a SKILL.md file. It becomes a /command, and Claude can also load it on its own when your request matches its description. Custom commands in .claude/commands/*.md still work, but skills replace them.

Scope Path
You, all projects ~/.claude/skills/<name>/SKILL.md
Project (commit it) .claude/skills/<name>/SKILL.md
Plugin <plugin>/skills/<name>/SKILL.md, run as /plugin-name:name

Example: summarise your changes

~/.claude/skills/summarize-changes/SKILL.md:

---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---

## Current changes

!`git diff HEAD`

## Instructions

Summarize the changes above in two or three bullet points, then list any risks
such as missing error handling, hardcoded values, or tests that need updating.
If the diff is empty, say there are no uncommitted changes.

The !`command` line runs before Claude reads the skill and inlines its output, so the skill arrives with live data. Run it with /summarize-changes, or just ask “what did I change?” and Claude picks it up from the description.

Example: a command with arguments

.claude/skills/fix-issue/SKILL.md:

---
name: fix-issue
description: Fix a GitHub issue end to end
argument-hint: "[issue-number]"
disable-model-invocation: true
allowed-tools: Bash(gh issue view *), Bash(npm test *)
---

Fix GitHub issue $ARGUMENTS following our coding standards.

1. Read the issue with `gh issue view $ARGUMENTS`
2. Find the relevant code and reproduce the problem
3. Implement the fix and add a regression test
4. Run the test suite until it passes
5. Commit with a message that references the issue

/fix-issue 123 substitutes 123 for $ARGUMENTS. Use $0, $1, and so on for positional arguments.

Useful frontmatter fields:

Field Effect
description When Claude should use it. Write it carefully; it drives automatic loading.
disable-model-invocation: true Only you can trigger it (good for anything with side effects)
user-invocable: false Hidden from the / menu; Claude-only background knowledge
allowed-tools Tools pre-approved while the skill runs
context: fork Run the skill in a subagent, keeping your main context clean
model, paths Override the model; only activate for matching files

Skills are cheap. Claude only sees each skill’s description until one is used, then loads the full text.

Subagents

A subagent is a specialist Claude can hand work to. It runs in its own context window with its own system prompt and tool limits, then returns a summary. Use one to keep noisy work (big searches, logs, test output) out of your main conversation, or to enforce limits such as read-only access.

Scope Path
Project .claude/agents/<name>.md
You, all projects ~/.claude/agents/<name>.md

.claude/agents/code-reviewer.md:

---
name: code-reviewer
description: Reviews code for bugs, security issues and readability. Use proactively after any significant change.
tools: Read, Grep, Glob
model: sonnet
---

You are a senior code reviewer. For the changed code:
1. Flag bugs and security issues first, with file:line references
2. Then readability and naming
3. For each issue, show the current code and a concrete fix
Never edit files; report only.

Because tools lists only read tools, this reviewer cannot modify anything. Ways to use it:

use the code-reviewer subagent to review my last commit
@"code-reviewer (agent)" look at the auth changes

Claude also delegates on its own when a task matches the description; “use proactively” encourages that. You can also start a whole session as an agent with claude --agent code-reviewer.

Other frontmatter fields: model (sonnet, opus, haiku, or inherit), permissionMode, maxTurns, skills (preload skills), mcpServers, isolation: worktree (work in a throwaway git worktree) and background: true.

Hooks: guarantees, not suggestions

CLAUDE.md asks Claude to do things. Hooks make them happen. A hook is a shell command that Claude Code runs at a lifecycle event. It’s configured in a settings file and runs every time, whatever the model decides.

Key events:

Event Fires Can block?
SessionStart Session begins or resumes No (stdout is added as context)
UserPromptSubmit Before your prompt is processed Yes
PreToolUse Before a tool runs Yes
PostToolUse After a tool succeeds Feedback only
Notification Claude needs your attention No
Stop Claude finishes responding Yes (keep going)
PreCompact / PostCompact Around context compaction No

A hook receives the event as JSON on stdin and answers with its exit code:

  • exit 0: no objection. The normal permission flow continues.
  • exit 2: block. Whatever you print to stderr is sent to Claude as the reason, so it can adjust.
  • Anything else: a non-blocking error.

Example: auto-format every edit

.claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
          }
        ]
      }
    ]
  }
}

Example: protect sensitive files

.claude/hooks/protect-files.sh (run chmod +x on it):

#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2
  fi
done
exit 0

Register it:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh" }
        ]
      }
    ]
  }
}

Now an attempt to edit .env is stopped before it happens, and Claude is told why. Run /hooks to see what’s configured, and claude --debug to watch hooks fire. Hooks can also be http (POST the event to a URL), prompt (a quick model judgement) or agent.

Hooks run with your user’s permissions. Read any hook script before you enable it, especially one that arrives in a cloned repo.

Plugins: package and share

A plugin bundles skills, subagents, hooks and MCP servers into one installable unit.

Install one

Run /plugin in a session and browse the Discover tab. Anthropic’s official marketplace is added automatically. Plugins install at user, project (shared through .claude/settings.json) or local scope, and can be disabled without uninstalling.

An installed plugin runs code as you. Install from sources you trust, and check its context cost in /plugin before installing.

Build one

my-plugin/
├── .claude-plugin/
│   └── plugin.json        # manifest (only this goes in .claude-plugin/)
├── skills/
│   └── review/SKILL.md    # → /my-plugin:review
├── agents/
│   └── reviewer.md
├── hooks/
│   └── hooks.json         # same shape as "hooks" in settings
└── .mcp.json              # MCP servers (part 4)

.claude-plugin/plugin.json:

{
  "name": "my-plugin",
  "description": "Team review workflow: reviewer agent, /review skill, format-on-save hook",
  "version": "1.0.0",
  "author": { "name": "Lone Elixir" }
}

Validate it and try it without installing:

claude plugin validate ./my-plugin
claude --plugin-dir ./my-plugin

After editing files, run /reload-plugins. To share it, hand over the folder, publish it in your own marketplace (a repo with .claude-plugin/marketplace.json), or submit it to Anthropic’s community marketplace. If you already have a .claude/ setup, copy its skills/, agents/ and hooks into a plugin folder and it works as-is, with names prefixed by the plugin name.

Headless: Claude in scripts and CI

claude -p runs one task non-interactively and exits with a normal exit code, so it fits anywhere a CLI does.

# Ask a question
claude -p "What does the auth module do?"

# Pipe data in, write the answer out
cat build-error.txt | claude -p "concisely explain the root cause" > why.txt

# Let it act, pre-approving only what it needs
claude -p "Run the test suite and fix any failures" --allowedTools "Bash,Read,Edit"

# Structured output for scripts
claude -p "Summarize this project" --output-format json | jq -r '.result'

# Enforce a JSON shape
claude -p "List the exported functions in auth.py" --output-format json \
  --json-schema '{"type":"object","properties":{"functions":{"type":"array","items":{"type":"string"}}},"required":["functions"]}' \
  | jq '.structured_output'

For CI, add --bare. It skips auto-loading of hooks, plugins, MCP servers and CLAUDE.md, so a run behaves the same on every machine; pass what you need explicitly with --settings, --mcp-config or --append-system-prompt. Bare mode needs ANTHROPIC_API_KEY. Use --permission-mode dontAsk (deny anything unapproved) or acceptEdits to set the baseline.

As a package.json script, this makes a project-specific linter:

{
  "scripts": {
    "lint:claude": "git diff main | claude -p \"you are a typo linter. for each typo report filename:line and the issue. return nothing else.\""
  }
}

For full programmatic control (callbacks, streaming, typed messages), the Claude Agent SDK for Python and TypeScript exposes the same agent loop. There’s also a ready-made GitHub Actions integration.

Next

Part 4 connects Claude Code to the outside world with MCP: using existing servers, then building your own in Python and TypeScript.