DEV Community

Cover image for Claude Code Skills vs Subagents vs Hooks vs Workflows: Which to Use in 2026
 Remy B.
Remy B.

Posted on Originally published at vibeready.sh AI-assisted

Claude Code Skills vs Subagents vs Hooks vs Workflows: Which to Use in 2026

Key Takeaways

  • A skill is text Claude reads in your window. A subagent is a second Claude with its own window. That one difference decides everything else.
  • CLAUDE.md, rules, and skills are requests. A hook is a guarantee: it fires on a lifecycle event whether Claude agrees or not, and exit code 2 blocks the action.
  • Slash commands are skills now. Plugins aren't a primitive at all, they're the box you ship the others in.
  • A workflow moves the plan out of Claude's head and into a script: up to 1,000 agents per run, 16 at a time, one result back.
  • Every primitive encodes an assumption about what the model can't do alone. Re-test that assumption after each model release, and delete what no longer earns its place.

Claude Code now has eight ways to change how it behaves, and most teams use one. In a study of 2,853 public repositories with agent configuration files, 90.6% had a context file like CLAUDE.md, 5.5% had a skill, and 4.6% had a subagent (arXiv, February 2026). Most teams wrote down their conventions and stopped there.

What follows is the decision table I wanted when I started: all eight primitives in one place, then a head-to-head for each pair people actually search for. Every example below is a file I actually run, not a hypothetical.

For months my CLAUDE.md had a line telling Claude never to touch .env. It held maybe nine times out of ten, which sounds fine until the tenth time rewrites a key I then have to rotate. I moved those four words into a PreToolUse hook that exits 2 on a path match, and the failure rate went to zero, because a hook doesn't read the instruction, it just refuses the write. The line in CLAUDE.md wasn't wrong. It was in the wrong primitive.

The Eight Primitives in One Table

The official docs spread these across six comparison tabs and three pages. Here they are in one place. Read the last column first: it's the one that tells you what you can actually rely on.

Who triggers it Where it runs What it costs you What's guaranteed
CLAUDE.md Session start, always Your window Every request, all session Nothing. It's context
Rules Session start, or when a matching file is touched Your window Only when matched Nothing. It's context
Skill You type /name, or Claude matches the description Your window (or a fork) Description always; body on use Nothing. Claude interprets it
Subagent Claude delegates, or you @-mention it Its own window Only the summary comes back Isolation. Your window stays clean
Hook A lifecycle event fires Outside Claude entirely Zero unless it returns output It runs. Every time
Plugin You install it Wherever its contents run The sum of its parts Same setup in every repo
Workflow You say ultracode or run a saved /name A script, in the background Many agents; one result in your window The script holds the plan, not Claude
Agent team You ask for teammates (experimental) Separate full sessions A full context window each Peers can argue with each other

Slash commands aren't a row because they're skills you invoke by name. More on that below.

For the four rows that perform work, the question that separates them is who holds the plan. In a skill, Claude does, following your text, inside your window. In a subagent, Claude does, turn by turn, in a separate window. In an agent team, a lead session does, across peers. In a workflow, a script does, and Claude only sees the final answer. Hooks are the odd one out: nobody holds a plan. The event fires and your code runs.

Here's what that looks like on one real job, adding a rate limiter to an API route in a Next.js SaaS. A rule says every route validates input. A skill, /new-api-route, carries the twelve-step procedure. A subagent, code-reviewer, reads the diff in its own window and returns three findings. A hook runs Prettier on every file Claude touched, without asking. Four primitives on one feature, and none of them overlap.

Skills vs Subagents: Context Is the Whole Difference

A skill is text Claude reads. A subagent is a second Claude.

A skill's description sits in your context every session, capped at 1,536 characters in the docs' skill listing, and its body loads when it's used. Everything it does happens in your window, with your history and your tools. A subagent gets its own window, its own system prompt, its own tool list, and often its own model. It does the work and sends back a summary. That's the whole difference, and it decides everything else: what each one costs, what it can see, and what it can break.

My code-reviewer is the cleanest example I have, because it's both at once. The subagent is the reviewer; the skills are its checklists.

docs/ai-context/agents/code-reviewer.md
---
name: code-reviewer
description: Review code for architecture, performance, patterns, and quality.
  Use proactively after implementing features or when reviewing changes.
tools: Read, Glob, Grep, Bash
model: opus
skills:
  - code-review
  - security-reviewer
---

You are a code review specialist. Review all files in the current change set
for architecture compliance, performance issues, pattern violations, and code
quality. Produce a severity-based report.
Enter fullscreen mode Exit fullscreen mode

When Claude hands a 40-file diff to this subagent, the 40 files load into its window. Yours gets six findings back. The two skills it preloads are the same ones you could run yourself with /code-review in the main conversation. Same checklist, different room.

So the rule is short. Use a skill when Claude should know something or follow a procedure: how we write API routes, the release checklist, the naming conventions. Use a subagent when the work produces output you won't read again (searches, logs, review noise), or when it needs a different model or a narrower tool set than your session has. The documented defaults: 20 subagents running at once, nested up to three layers deep.

Two practitioners, same idea. Simon Willison keeps a standing instruction to "use your judgement to decide an appropriate lower power model and run that in a subagent" for coding tasks: judgment stays on the main loop, the typing gets delegated (Willison, July 2026). Addy Osmani's version: subagents burn more tokens, "so spend them where a second opinion is worth paying for" (Osmani, June 2026). Both are buying the same thing: a clean window, and a reviewer who isn't the author.

The bridge between the two is context: fork. Add it to a skill's frontmatter and the skill's text becomes the prompt for a subagent instead of loading into your window. One thing that trips people: a forked skill runs in the background by default, so set background: false to wait for its result. The anatomy of a SKILL.md itself, including the trick that injects a live git diff before Claude reads it, is in the loop engineering guide.

Hooks vs Skills: A Request vs a Guarantee

Everything above is a request. Claude reads it and usually complies. A hook is not a request.

The official memory docs say it plainly: Claude treats CLAUDE.md and auto memory "as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead." Anthropic's own guide to steering Claude Code draws the line in one sentence: "The model choosing to run a formatter is different from the formatter running automatically" (Anthropic, June 2026).

A hook is an entry in settings.json: an event, a matcher, and a command. The command gets the event as JSON on stdin and answers with an exit code. Exit 0 means no objection. Exit 2 means blocked, and whatever you wrote to stderr goes back to Claude as the reason. Here's the one I'd put in a SaaS repo first.

Refuse edits to files that should never change in a session (PreToolUse)

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

for pattern in ".env" "prisma/migrations/" "terraform/"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
    exit 2    # exit 2 = block it; stderr becomes Claude's feedback
  fi
done
exit 0        # exit 0 = no objection; the normal permission flow applies
Enter fullscreen mode Exit fullscreen mode
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Secrets, applied migrations, infrastructure. Ask Claude to "add a comment to .env" and the edit is blocked before it happens, with the script's message handed back so Claude adjusts instead of retrying. The same shape on a PostToolUse event runs Prettier on every file Claude touches.

The gate doesn't have to live inside the session. A git pre-commit hook or a CI check is a deterministic gate too; the difference is timing. A Claude Code hook stops a bad edit before it exists, a CI gate stops it before it merges, and most teams end up wanting both. Either way the rule is the same: conventions go in CLAUDE.md, guardrails go in hooks.

Hook output lands in context, so hooks and skills pair up naturally: the hook runs the linter, and a /fix-lint skill tells Claude how to resolve what it found.

Slash Commands Are Skills Now

If you learned Claude Code in 2025, you wrote commands as flat Markdown files in .claude/commands/. Those still work, but custom commands have been merged into skills. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and behave the same way.

What the skill form adds is a folder for supporting files and two frontmatter switches that answer the question "who is allowed to fire this?"

---
name: deploy
description: Deploy the current branch to Cloud Run after the release checklist passes.
disable-model-invocation: true     # only you can run /deploy; Claude never auto-invokes it
---
Enter fullscreen mode Exit fullscreen mode

disable-model-invocation: true is for anything with side effects: deploys, releases, database migrations. Claude can't reach for it on its own, and it costs zero context until you type it. The mirror switch, user-invocable: false, hides a skill from the / menu so only Claude can load it, right for reference material you'd never invoke by hand. The built-in commands you already use, like /loop and /goal, are the same mechanism shipped by Anthropic.

Plugins Are Packaging, Not a Primitive

A plugin doesn't do anything by itself. It's a folder with a manifest at .claude-plugin/plugin.json and any mix of skills/, agents/, hooks/hooks.json, and an .mcp.json, plus newer slots for LSP servers, background monitors, and saved workflows. Install it and every part inside becomes available, with skills namespaced as /plugin-name:skill so two plugins can both ship a /review.

The trigger for building one is specific: a second repository needs the same setup. Until then, keep everything standalone in .claude/. When the day comes, claude plugin init my-kit scaffolds the manifest, and claude --plugin-dir ./my-kit loads it for testing without installing.

I don't ship my own kit as a plugin, and the reason says something about where plugins stop. A plugin distributes to Claude Code. My kit has to work in Cursor and Windsurf too, so one make ai-setup target does the plugin's job across tools: it copies the 14 master rules into .claude/rules/, .cursor/rules/*.mdc, and .windsurf/rules/, each in that tool's frontmatter dialect, and symlinks CLAUDE.md to AGENTS.md so all of them read one core file. That's the same "same setup, every repo" promise, one layer up. Where the instruction files themselves should live is its own decision.

Workflows vs Subagents: One Helper, or a Script That Runs Many

A subagent is one helper you send off. A workflow is a script that sends off many helpers and hands you one answer. Claude writes the script for the task you describe, you approve it, and it runs in the background while you keep working. Nothing in between lands in your window.

Workflows shipped on May 28, 2026, in v2.1.154, the same day as Opus 4.8. You start one by putting ultracode in your prompt (the trigger word was workflow until a June 1 rename) or by asking for a workflow in plain words (Claude Code changelog). Two numbers to keep in mind: a run can use up to 1,000 agents, but only 16 run at once. "Hundreds of parallel agents" means hundreds per run, not at the same time.

Here's one on a problem every multi-tenant SaaS has: making sure no API route forgets to scope its queries by organization.

> ultracode: audit every route handler under src/app/api for Prisma queries that
  skip organizationId scoping, and adversarially verify each finding
Enter fullscreen mode Exit fullscreen mode
// .claude/workflows/audit-org-scoping.js  (saved from /workflows with s)
export const meta = {
  name: 'audit-org-scoping',
  description: 'Find API routes that skip organizationId scoping, then verify each finding',
}

const found = await agent('List every route.ts under src/app/api/.', {
  schema: { type: 'object', required: ['files'],
            properties: { files: { type: 'array', items: { type: 'string' } } } },
})

const findings = await pipeline(found.files, file =>
  agent(`Check ${file}: is every Prisma query scoped by organizationId? Report violations.`, { label: file }),
)

const verified = await pipeline(findings.filter(Boolean), f =>
  agent(`Adversarially verify this finding. Return null if it does not hold: ${JSON.stringify(f)}`, { label: 'verify' }),
)

return verified.filter(Boolean)
Enter fullscreen mode Exit fullscreen mode

Read it top to bottom. One agent lists the routes. One agent per route checks it. One agent per finding tries to knock it down. You get the findings that survived, and next quarter the same script runs again as /audit-org-scoping. That last step, a second agent checking the first, is what a plain subagent fan-out doesn't give you. It's the same idea as the separate judge in my eval suite.

One limit to know before your first run: a workflow can't stop to ask you anything. If you want to sign off between stages, run two workflows. And try it on one directory first, because a run can burn far more tokens than doing the task in conversation.

The biggest public example is Bun's rewrite from Zig to Rust: about a million lines in 11 days, roughly 50 workflows, about $165,000 at API pricing, and 19 known regressions, with Bun disclosing that Anthropic had acquired it and that the work used a pre-release model (Bun, July 2026). Zig's creator, Andrew Kelley, called the output "[a] million lines of unreviewed slop" (The Register, July 2026). Both facts hold. A workflow can write a million lines. Reviewing them is still your job.

The rule: a focused side task is a subagent. A job too big for a few of them, or one where you want the findings checked before you see them, or one you'll run again, is a workflow.

Subagents vs Agent Teams

A subagent works inside your session and reports back to it. An agent team is several full Claude Code sessions with a shared task list that message each other directly. Teams are experimental, off by default, and switched on with CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. Each teammate costs a full context window, so the docs say start with three to five. Use one when the workers need to argue: five teammates trying to disprove each other's theory about a bug is a team job. A review from three angles that only needs to report back is still subagents.

Which Primitive When: The Trigger Table for a SaaS Codebase

Don't pick from the feature list. Pick from the symptom. Each primitive has a moment where it becomes obvious, and here they are with the version I hit building a multi-tenant Next.js product.

The symptom In practice
Rule Claude forgot the same convention twice "Every Prisma query is scoped by organizationId" went into the database rule, matched to the data layer, instead of being repeated in chat
Skill You pasted the same procedure a third time The PR checklist became /pr-create; the migration steps became /db-migrate
Subagent A side task flooded your window with output Review moved into code-reviewer; QA moved into qa-tester. The main window sees findings, not files
Hook Something must happen every time, no judgment needed Formatting on every edit; a block on .env, applied migrations, and Terraform
Plugin A second repo needs the same setup For Claude Code alone, a plugin. For Cursor and Windsurf too, a script that copies the rules into each tool's folder
Workflow The job is bigger than a few agents, or you want findings verified Auditing every route handler for scoping, with a verification pass
Agent team The workers need to argue with each other Competing hypotheses on a flaky-connection bug. Rare, and experimental

The same table tells you when to update what you have. A repeated review comment is a rule edit, not another chat correction. A skill you keep tweaking by hand needs another revision. And a rule Claude follows fine without the rule is a rule to delete.

What a Working Setup Looks Like

Most repos need three of the eight, arranged so each one carries a different kind of knowledge. Rules hold conventions, one topic per file, scoped to the paths they describe. Skills hold procedures, named after the verb. Subagents wrap a skill or two in a narrower tool list and their own window. A hook or two guards the few things that must never happen. That's the whole setup, and it fits in four places.

.claude/
├── rules/database.md               # "every query is scoped by organizationId"; loads with the data layer
├── skills/new-api-route/SKILL.md   # the twelve-step procedure, run as /new-api-route
├── agents/code-reviewer.md         # preloads code-review + security-reviewer; read-only tools; own window
└── settings.json                   # the protect-files hook from above
Enter fullscreen mode Exit fullscreen mode

The order you add them matters less than keeping each thing in its lane. A convention that ends up in a skill gets skipped whenever the skill isn't invoked. A procedure that ends up in CLAUDE.md costs context on every request. A guardrail that lives anywhere but a hook is a suggestion. This is the arrangement I ship, and it works the same whether you write it yourself or start from a kit; rules and skills carry over to Cursor and Windsurf, subagents are Claude Code only.

Remove What the Model No Longer Needs

Every rule, skill, and subagent you add is a patch for something the model couldn't do reliably at the time. Models improve. A setup that only ever grows ends up carrying patches for problems that are gone: a rule that costs context on every request to prevent a mistake the model stopped making, or a review subagent that costs tokens to catch what the model now gets right the first time.

Anthropic's own harness team hit this at scale. Their setup added a second agent to judge the first one's work, because agents asked to grade themselves "tend to respond by confidently praising the work." It helped, but the full arrangement cost over twenty times a solo run, $200 against $9, and only paid off on tasks the model couldn't do alone. When Opus 4.6 arrived, they deleted an entire component the new model no longer needed (Anthropic Engineering, March 2026).

The practical version is simple. After each model release, pick one rule or skill and turn it off for a week. If nothing regresses, delete it. Your harness should get smaller as the model gets better, and the only way to know whether yours does is to test it.

Official docs for each primitive

Skills · Subagents · Hooks reference · Plugins · Dynamic workflows · Agent teams · CLAUDE.md and rules · Extend Claude Code (the official comparison)

Top comments (0)