The Plugin System — Commands, Agents, Skills, and Hooks
Prerequisites
- ›Article 1: Architecture Overview
- ›Understanding of YAML and Markdown frontmatter
- ›Basic shell scripting knowledge
The Plugin System — Commands, Agents, Skills, and Hooks
As we saw in Part 1, the claude-code repository houses 13 official plugins built on a convention-over-configuration architecture. But understanding the directory layout is different from understanding how these components actually work. Each of the four component types — commands, agents, skills, and hooks — has its own discovery mechanism, configuration surface, and execution semantics.
This article examines each type using real examples from the official plugins, covers the critical distinction between plugin and settings hook formats, and synthesizes everything into a unified lifecycle model.
Commands: User-Invoked Workflows
Commands are the primary entry point for plugin functionality. They're Markdown files with YAML frontmatter that become slash commands in Claude Code. When a user types /feature-dev, Claude Code loads the corresponding .md file and uses its content as the prompt instruction.
The frontmatter is where things get interesting. Here's the feature-dev command:
plugins/feature-dev/commands/feature-dev.md#L1-L4
---
description: Guided feature development with codebase understanding and architecture focus
argument-hint: Optional feature description
---
The description appears in command listings. The argument-hint tells users what to pass after the command name. But look at code-review's frontmatter for more powerful configuration:
plugins/code-review/commands/code-review.md#L1-L4
---
allowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*), mcp__github_inline_comment__create_inline_comment
description: Code review a pull request
---
The allowed-tools field is a whitelist with pattern matching. Bash(gh pr view:*) means "allow Bash tool calls, but only when the command starts with gh pr view." This is how plugins constrain their agents to specific operations — a code review plugin shouldn't be writing files or running arbitrary commands.
The ralph-loop command demonstrates two additional frontmatter fields:
plugins/ralph-wiggum/commands/ralph-loop.md#L1-L5
---
description: "Start Ralph Wiggum loop in current session"
argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
allowed-tools: ["Bash(${CLAUDE_PLUGIN_ROOT}/scripts/setup-ralph-loop.sh:*)"]
hide-from-slash-command-tool: "true"
---
The hide-from-slash-command-tool prevents Claude from autonomously invoking this command — it must be user-initiated. And notice how allowed-tools uses ${CLAUDE_PLUGIN_ROOT} to restrict Bash execution to a specific script within the plugin.
Tip: The
$ARGUMENTSvariable in command body text is replaced with whatever the user types after the slash command. This is how commands accept input without complex argument parsing.
Agents: Specialized AI Workers
Agents are subagent definitions — specialized AI workers that commands orchestrate. Like commands, they're Markdown files with YAML frontmatter, but the frontmatter configures the model and capabilities of the subagent rather than user-facing metadata.
Here's the code-explorer agent from feature-dev:
plugins/feature-dev/agents/code-explorer.md#L1-L7
---
name: code-explorer
description: Deeply analyzes existing codebase features by tracing execution paths...
tools: Glob, Grep, LS, Read, NotebookRead, WebFetch, TodoWrite, WebSearch, KillShell, BashOutput
model: sonnet
color: yellow
---
Each frontmatter field serves a distinct purpose:
| Field | Purpose | Example |
|---|---|---|
name |
Display name in logs/UI | code-explorer |
description |
When to use this agent | Detailed capability description |
tools |
Allowed tool whitelist | Glob, Grep, LS, Read, ... |
model |
Model tier selection | sonnet, haiku, opus |
color |
Visual identification in logs | yellow, green, red |
Compare the three feature-dev agents to see how model selection and tool restrictions create focused roles:
| Agent | Model | Tools | Role |
|---|---|---|---|
code-explorer |
sonnet | Read-heavy (Glob, Grep, Read...) | Fast codebase analysis |
code-architect |
sonnet | Same read-heavy set | Architecture design |
code-reviewer |
sonnet | Same read-heavy set | Quality review with confidence scoring |
The code-reviewer agent at plugins/feature-dev/agents/code-reviewer.md#L25-L33 introduces a confidence scoring system (0–100 scale) where only issues scoring ≥80 are reported. This filtering mechanism is implemented purely through the agent's prompt instructions — no special runtime support needed. The prompt is the program.
flowchart LR
CMD["/feature-dev command"] --> EXPLORE["code-explorer<br/>model: sonnet<br/>color: yellow"]
CMD --> ARCH["code-architect<br/>model: sonnet<br/>color: green"]
CMD --> REVIEW["code-reviewer<br/>model: sonnet<br/>color: red"]
EXPLORE --> |"Read-only tools"| ANALYSIS["Codebase<br/>Analysis"]
ARCH --> |"Read-only tools"| DESIGN["Architecture<br/>Design"]
REVIEW --> |"Read-only tools"| QUALITY["Quality<br/>Review"]
style EXPLORE fill:#FDD835
style ARCH fill:#4CAF50,color:#fff
style REVIEW fill:#E53935,color:#fff
Skills: Context-Activated Knowledge
Skills are the most passive component type. They provide knowledge that Claude Code automatically activates based on context matching — no explicit invocation required. Each skill lives in its own subdirectory with a SKILL.md file.
Here's the frontend-design skill's frontmatter:
plugins/frontend-design/skills/frontend-design/SKILL.md#L1-L5
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications.
license: Complete terms in LICENSE.txt
---
The description field does double duty: it documents the skill and tells Claude Code when to activate it. When a user asks about building a web component, Claude Code matches that context against skill descriptions and injects the relevant skill content.
The plugin-structure skill at plugins/plugin-dev/skills/plugin-structure/SKILL.md#L1-L5 has a much more explicit description:
description: This skill should be used when the user asks to "create a plugin", "scaffold a plugin", "understand plugin structure"...
This longer description casts a wider activation net. The skill author controls specificity — a vague description activates broadly, a precise one activates narrowly.
Skills can include supporting material in subdirectories: references/, examples/, scripts/. The plugin-dev plugin has seven skills, each with its own reference material. This is where skills differ fundamentally from commands: a command is a single-shot instruction, but a skill is a knowledge base that persists across the session.
Hooks: Event-Driven Interceptors
Hooks are the most powerful component type — and the most complex. They execute in response to nine lifecycle events, and they can modify Claude Code's behavior by blocking operations, injecting context, or enforcing policies.
The nine hook events, documented at plugins/plugin-dev/skills/hook-development/SKILL.md#L634-L644:
| Event | When | Common Use |
|---|---|---|
PreToolUse |
Before tool executes | Validation, blocking |
PostToolUse |
After tool completes | Feedback, logging |
Stop |
Agent considers stopping | Completeness checks |
SubagentStop |
Subagent considers stopping | Task validation |
SessionStart |
Session begins | Context loading |
SessionEnd |
Session ends | Cleanup |
UserPromptSubmit |
User sends prompt | Input validation |
PreCompact |
Before context compaction | Preserve critical info |
Notification |
Notification sent | Reactions, logging |
Hooks come in two types: command hooks (shell scripts for deterministic checks) and prompt hooks (LLM-driven for context-aware decisions). The exit code protocol is critical:
- Exit 0 — Allow the operation (stdout shown in transcript)
- Exit 2 — Block the operation (stderr fed back to Claude)
- Other — Non-blocking error
Here's a sequence showing how a PreToolUse hook intercepts a file write:
sequenceDiagram
participant C as Claude Code
participant H as PreToolUse Hook
participant T as Write Tool
C->>H: JSON via stdin (tool_name, tool_input)
H->>H: Check patterns/rules
alt Pattern matches (new warning)
H-->>C: stderr: warning message
H->>C: exit 2 (BLOCK)
Note over C: Tool call blocked
else No match or already warned
H->>C: exit 0 (ALLOW)
C->>T: Execute Write tool
T->>C: Tool result
end
Matchers let you filter which tools trigger a hook. The security-guidance plugin uses "matcher": "Edit|Write|MultiEdit" to fire only on file modification tools:
plugins/security-guidance/hooks/hooks.json#L1-L16
Matchers support exact match ("Write"), pipe-separated OR ("Edit|Write"), wildcard ("*"), and regex patterns ("mcp__.*__delete.*").
The Dual Hook Format Gotcha
This is the single most common developer pitfall when working with hooks. There are two different JSON formats depending on where your hooks live, and using the wrong one silently fails.
Plugin format (in hooks/hooks.json inside a plugin) wraps events inside a hooks key:
{
"description": "Optional description",
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/validate.sh"
}
]
}
]
}
}
Settings format (in .claude/settings.json or user settings) puts events directly at the top level:
{
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "python3 /path/to/validator.py"
}
]
}
]
}
You can see the plugin format in hookify's registration at plugins/hookify/hooks/hooks.json#L1-L49 — note the outer "hooks" wrapper. The bash_command_validator example at examples/hooks/bash_command_validator_example.py#L13-L27 documents the plugin format in its docstring.
Tip: If your hook isn't firing, check the format first. Plugin
hooks.jsonneeds the{"hooks": {...}}wrapper. User settings don't. The hook development skill atplugins/plugin-dev/skills/hook-development/SKILL.mddocuments both formats side by side.
Component Lifecycle: Discovery to Execution
With all four component types understood, here's how they fit together in the unified lifecycle:
flowchart TD
INSTALL["Plugin Installed"] --> READ["Read plugin.json manifest"]
READ --> SCAN["Scan Convention Directories"]
SCAN --> CMD_SCAN["commands/*.md"]
SCAN --> AGT_SCAN["agents/*.md"]
SCAN --> SKL_SCAN["skills/*/SKILL.md"]
SCAN --> HK_SCAN["hooks/hooks.json"]
SCAN --> MCP_SCAN[".mcp.json"]
CMD_SCAN --> CMD_REG["Register as<br/>slash commands"]
AGT_SCAN --> AGT_REG["Register as<br/>available agents"]
SKL_SCAN --> SKL_REG["Register for<br/>context matching"]
HK_SCAN --> HK_REG["Register on<br/>event bus"]
MCP_SCAN --> MCP_REG["Start MCP<br/>servers"]
CMD_REG --> USER["User types /command"]
AGT_REG --> ORCH["Command orchestrates agents"]
SKL_REG --> AUTO["Auto-activated by context"]
HK_REG --> EVENT["Fired by lifecycle events"]
MCP_REG --> TOOL["Available as tools"]
USER --> EXEC["Execution"]
ORCH --> EXEC
AUTO --> EXEC
EVENT --> EXEC
TOOL --> EXEC
style INSTALL fill:#FF5722,color:#fff
style EXEC fill:#4CAF50,color:#fff
The key insight: commands are user-initiated, agents are command-orchestrated, skills are context-activated, and hooks are event-driven. These four invocation models cover the full spectrum from explicit user control to fully automatic behavior.
Custom paths in plugin.json supplement (not replace) default directories. If you specify "commands": "./custom-commands", Claude Code scans both commands/ and custom-commands/. This additive behavior means you can extend the convention without breaking it.
Changes take effect on the next Claude Code session — no hot-reloading of plugin configurations. Edit your hooks.json, restart Claude Code, and the new hooks load.
What's Next
Now that we understand the component model, we're ready to see it orchestrated at scale. In Part 3, we'll examine two sophisticated multi-agent patterns: feature-dev's seven-phase human-in-the-loop workflow with parallel agent launches, and code-review's multi-pass validation pipeline where findings are individually verified by fresh subagents. We'll also dissect the ralph-wiggum plugin's creative self-referential loop pattern — a Stop hook that reads the transcript and re-injects the original prompt to create iterative agent execution.