Every time I want Claude Code to do something new, I hit the same fork. The work itself is obvious. Where to put it is not.
There are four places it can go, and they are not interchangeable. Pick wrong and the thing either never runs, runs at the wrong moment, or quietly bills you context on every turn for the rest of the session.
Extending Claude Code
is a choice between four attachment points — a hook, a skill, a subagent, or an MCP server — each of which fires at a different moment in the agent loop and fails in a different way.
Disclosure before I go further. I build Codelit, which has an agent workflow engine of its own with versions of some of these primitives: skills with activation rules, MCP servers with declared tool boundaries, model routing, approval gates. It loses to Claude Code on the exact axis this post turns on. Codelit has no hook — no deterministic, non-model gate that fires before every step whether the model wants it or not. Approval gates pause a run and wait for a person, which is useful and is not the same thing. If you need something checked on every action with nobody in the loop, Claude Code does that and my engine does not.
What is the difference between a skill, a subagent, a hook and an MCP server?
They differ by who pulls the trigger and where the output lands. A hook is fired by an event and can block. A skill is chosen by the model and injects instructions inline. A subagent is chosen by the model and gets a separate context window. An MCP server is reached through a tool call and touches systems outside your machine.
Hooks bind a handler to a lifecycle event. The events span the session — SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PreCompact, SubagentStop and a long tail more — and a handler can be a shell command, an HTTP endpoint, an MCP tool, a prompt, or an agent. The model does not get a vote. That is the whole point of them.
Skills are a folder with a SKILL.md inside it. This is not a Claude Code feature; it is the Agent Skills open standard, originally developed by Anthropic and released openly, and the client showcase at agentskills.io in July 2026 lists Cursor, GitHub Copilot, VS Code, Gemini CLI and OpenAI Codex among the tools reading the same format. Loading happens in three stages: at startup the agent sees only the name and description, when a task matches it reads the full SKILL.md, and bundled scripts load only if the instructions call for them.
Subagents are markdown files with YAML frontmatter in .claude/agents/ for a project or ~/.claude/agents/ for you. The frontmatter is where the useful controls live: tools as an allowlist, disallowedTools as a denylist, model, permissionMode, maxTurns. Each runs in its own context window, sized by its own model rather than the parent's.
MCP servers speak the Model Context Protocol, an open standard for connecting agents to tools and data. Claude Code supports stdio for local processes and HTTP for remote ones; SSE still works but the docs mark it deprecated.
The decision table I actually use
| Mechanism | What it is | What makes it run | Standing context cost | Reach for it when |
|---|---|---|---|---|
| Hook | A command, HTTP call, MCP tool, prompt or agent bound to a lifecycle event | The event fires, every time, whatever the model would have preferred | Nothing until it runs | The check must happen every time and must not be skippable |
| Skill | A folder with a SKILL.md holding a procedure | The model matches your description, or you type the slash command | Name and description in the listing at startup, then the full body from first invocation onward | The steps are easy to state but the outcome needs judgment |
| Subagent | A separate agent with its own prompt, tools, model and window | The model delegates, or you invoke it directly | Its description only — the work never enters your window | The task will generate output you will never look at twice |
| MCP server | A process or endpoint exposing tools, resources and prompts over a standard protocol | A tool call, after Claude searches for and finds the tool | Tool names and server instructions at session start | The data or the action lives in someone else's system, behind a credential |
The column people skip is the cost column, and it is the one that bites three weeks later.
Why does a check that must never be skipped belong in a hook?
A check belongs in a hook when correctness cannot depend on the model choosing to run it. A hook fires on its event regardless, and a PreToolUse hook that exits with code 2 blocks the tool call, sending its stderr back to the model as the error message.
The clearest example in the repo behind this blog is scripts/lint-posts.mjs. It parses every post's frontmatter with the same gray-matter call the site itself uses, then checks a pile of yes-or-no rules: every related slug resolving to a real post, no internal link pointing at a page that does not exist, no heading containing markup that would break its anchor id, no H1 in the body because the template renders the title as the H1.
Not one judgment call in that list. Every rule has an answer a script can compute. It already runs inside npm run quality and behind a Husky pre-commit hook, and the reason it is a script rather than a paragraph of guidance is that a broken frontmatter parse takes the whole build down. That failure is not negotiable, so the check cannot be either.
What exit code 2 does depends on which event you attached to, and this trips people up:
| Hook event | What exit code 2 does | So it is good for |
|---|---|---|
| PreToolUse | Blocks the tool call before it runs | Refusing a destructive command outright |
| UserPromptSubmit | Blocks the prompt from being processed | Catching secrets or forbidden requests at the door |
| Stop and SubagentStop | Prevents the agent from finishing the turn | Forcing a build or test to pass before the agent claims it is done |
| PostToolUse | Nothing blocking — the tool has already run | Reporting, formatting, or annotating after the fact |
Why does a procedure with judgment in it belong in a skill?
A procedure belongs in a skill when the steps are easy to write down but the outcome still needs a judgment call, and when you would otherwise paste the same instructions into chat for the tenth time. The body loads only when a task matches it, so long reference material sits on disk costing nothing until the day it is needed.
The example I keep reaching for in this repo is visual verification. There is no Playwright dependency here, so the procedure is to borrow playwright-core from a sibling repo that already has the browsers cached, then screenshot in scroll steps of 350 pixels or less with at least 100 milliseconds between them — because the scroll-reveal sections sit at opacity 0 until they intersect, and a faster pass returns a stack of blank images.
That is a procedure with a trap in it, which is the first half of a good skill. The second half is that the real question — does this page look right — has no script that answers it. A hook can confirm the screenshot was captured. Only a reader can tell you the hero now sits three pixels below the fold on a 13-inch laptop.
Here is the cost most people miss. Once a skill is invoked, its rendered content enters the conversation as a single message and stays there for the rest of the session; Claude Code does not re-read the file on later turns. Every line in the body is a recurring charge, not a one-off. Write standing instructions, not a narration of your reasoning.
Discovery has a budget too, with a consequence that took me a while to see. Claude Code loads a listing of every skill's name and description, budgeted at roughly one percent of the model's context window, with each entry's combined description and trigger text capped at 1,536 characters. When the listing overflows, descriptions are dropped starting with the skills you invoke least. The skill you use twice a year is exactly the one that quietly loses the words that would have made it match.
When is a subagent worth the cost of re-explaining the task?
A subagent is worth it when the work will produce a mountain of intermediate output you never want to see again, and when the task can be fully stated in a paragraph. Each subagent starts with a fresh, isolated context window and cannot see your conversation history, the files already read, or the skills already invoked.
That isolation is the product and the price in one sentence. Checking a new draft for voice consistency against the twenty posts already on this blog means reading most of them: tens of thousands of words of input whose entire useful output is one line, either it sounds like me or these three sentences do not. Everything in between is landfill, and landfill in your main window is landfill compaction will later have to eat.
The price is that you cannot gesture at the conversation. If the task only makes sense given the last hour of back and forth, a named subagent is the wrong tool and a fork is the right one — a fork inherits the whole conversation while still keeping its tool calls out of your window.
There is a second reason to reach for one that has nothing to do with context. Frontmatter of tools: Read, Grep, Glob produces a reviewer structurally incapable of editing the thing it is reviewing, which is a stronger guarantee than asking nicely. And model: haiku on an exploration agent is the same instinct one layer down from Codelit's router across eleven AI providers: cheap models on classification, strong models on planning.
Why does live authenticated data belong in an MCP server?
MCP is the right answer when the thing you need lives inside someone else's system behind a credential, and no amount of instruction will conjure it. A skill can tell Claude exactly what to do with a Linear issue. Only an MCP server can go and fetch the issue.
Scope matters more than transport. There are three: local to you in this project, where experiments belong; project scope in a .mcp.json at the repo root, checked into version control so the whole team gets it; and user scope, which follows you everywhere. Pick project scope for anything a teammate would be annoyed to find out you had and they did not.
The old objection to MCP was context, and it has weakened. Tool search is on by default in current Claude Code, so only tool names and server instructions load at session start and full schemas arrive on demand; both are truncated at 2KB each anyway. The budget problem moved downstream to results. Claude Code warns when MCP tool output exceeds ten thousand tokens and caps it at twenty five thousand by default, so a chatty server floods your window with data, not definitions.
The objection that has not weakened is trust. The MCP specification is explicit that tool annotations are hints supplied by the provider and that clients must not make trust decisions from them — the finding ResolveMesh was built around, and the reason it launched with an empty catalog. A tool description is a claim, not a contract. Claude Code takes the same position structurally: subagents shipped inside a plugin have their hooks, mcpServers and permissionMode frontmatter ignored, because a plugin you installed should not be able to hand itself new servers and a looser permission mode. Adding an MCP server is a security question before it is an architecture question.
What breaks when you pick the wrong one?
Each wrong choice fails in its own recognisable way, which makes the failure mode the fastest diagnostic you have.
| You picked | You should have picked | How it shows up |
|---|---|---|
| A skill, or a line in CLAUDE.md | A hook | It works for a few turns and then quietly stops. The text is still in context; the model just chose otherwise |
| A hook | A skill | The check fires on cases it was never written for, and you widen the matcher until it matches nothing useful |
| A subagent | Inline work, or a fork | You burn the delegation message re-explaining the session, get an answer to a slightly different question, and do it yourself anyway |
| Inline work | A subagent | The main window fills with search results and file dumps, and compaction eats the part of the conversation you actually needed |
| An MCP server | A skill | You have built and now maintain a service to do something that was a paragraph of instructions and one shell command |
| A skill | An MCP server | Claude describes the current state of a system it cannot see, fluently and wrongly |
That last row is the expensive one, because it is the only failure on the list that looks like success.
How does this map onto Codelit's agent workflow engine?
Codelit's agent workflow engine names three of the four outright — skills with activation rules and risk labels, MCP servers with explicit tool, resource and prompt boundaries, and model routing that puts cheap models on classification and stronger ones on planning — alongside triggers, approval gates, secrets pulled from a vault at run time, and eval harnesses. It has no hook.
When you describe an agent in one sentence and the engine turns it into a spec, the work it does is this exact classification: which clause is a skill, which is an MCP boundary, which is a trigger, which needs a human. Same instinct as the pipeline that turns a prompt into a diagram, where validation and layout sit in stages that always run rather than stages the model chooses.
The missing hook is a genuine gap and I am not going to dress it up. Codelit's version of "this must always happen" is a run cap — twelve steps and a dollar of model cost, hard-stopped — plus an approval gate that pauses and emails a person. Those catch runaway cost and irreversible actions. They do not catch the case a hook exists for: a cheap deterministic check that runs on every tool call and needs nobody awake.
Which Claude Code version this was written against
Every capability claim here was checked against the Claude Code documentation at code.claude.com in July 2026, against version 2.1.218. This post has the shortest shelf life of anything on this blog and I will re-check it quarterly.
The specifics most likely to drift are the hook event list, the subagent frontmatter fields, whether tool search stays on by default, and the exact budget numbers. Two things I do not expect to move: there are four attachment points, and only one of them runs without the model's agreement.
If you remember one line, make it this. Put it in a hook when being skipped is unacceptable, a skill when explaining is the hard part, a subagent when the output is landfill, and an MCP server when the data is not yours. Three of those four fail quietly. The hook is the only one that fails loudly, which is a decent argument for using it in more places than you think you need to.
Questions people actually ask
- What is the difference between a Claude Code skill and a subagent?
- A skill injects instructions into the conversation you are already having. A subagent starts a separate conversation with its own context window, its own system prompt, and its own tool list, then returns only a summary. Use a skill when the agent needs to know how to do something. Use a subagent when the work will produce output you never want to read again.
- When should I use a hook instead of writing the rule in CLAUDE.md?
- Use a hook whenever being skipped is unacceptable. Text in CLAUDE.md or a skill is an instruction the model can choose to ignore, and the Claude Code docs say as much: if guidance stops influencing behaviour, use hooks to enforce it deterministically. A hook fires on its lifecycle event regardless of what the model decides, and a pre-tool hook can block the call outright.
- Do MCP servers use up my context window?
- Much less than they used to. Tool search is on by default in current Claude Code, so only tool names and server instructions load at session start and full schemas are fetched on demand. The bigger budget risk is now output rather than definitions. Claude Code warns when MCP tool output passes ten thousand tokens and caps it at twenty five thousand by default.
- Can a subagent see my conversation history?
- No. Each subagent starts with a fresh, isolated context window and does not see your conversation history, the files already read, or the skills already invoked. It works from a delegation message and its own system prompt. That isolation is the entire benefit and the entire cost. If a task only makes sense given the last hour of conversation, use a fork instead, which inherits everything.
- Where do Claude Code hooks and subagents get configured?
- Hooks live in settings files: a personal one in your home directory, a shared one checked into the project, a local untracked one, managed policy settings, plugin hook files, and skill or agent frontmatter. Subagents are markdown files with YAML frontmatter, stored per project or per user. Anything you want a team to inherit belongs in the checked-in project location.