A skill is the most flexible tool Claude Code gives you, and that flexibility is why people put the wrong things in it.
Search for claude code skills and the results are Reddit threads, awesome-lists and GitHub collections of skills to install. They answer “which skills should I have.” The question that decides whether your setup works is the one underneath: for a given piece of know-how, which of four places should it live in?
The hub post argues that Claude Code best practices are not advice, and that the ones that survive move out of the prompt into harness layers. Hooks are the layer that enforces. This post is about the layer that teaches, and about the three neighbours it keeps getting confused with.
Four places, four jobs
Anthropic’s own extension overview states the split in one sentence: CLAUDE.md handles always-on context, skills handle on-demand knowledge and workflows, MCP handles external connections, and hooks handle automation. The part worth drawing out is what each one is bad at.
| CLAUDE.md | Skill | MCP server | Hook | |
|---|---|---|---|---|
| Job | Remember: facts true every session | Teach: know-how for a matching task | Connect: access to a system | Enforce: a check the model cannot argue with |
| Loads | Full content, every session | Description every session, body when used | Tool names at start, schemas on demand | Nothing, runs outside the conversation |
| Who decides it applies | Always applies | The model, from the description (or you, with a slash command) | The model, when it needs the tool | The harness, on the event |
| Can it guarantee anything | No, it is context | No, the model interprets it | Only what the server permits | Yes |
| Fails when | It grows past what is read, or holds procedures | The description is vague, or it duplicates an integration | The scope is narrower than the task | It fires on things that do not matter |
The “can it guarantee anything” row says only one of these four has a guarantee. The “loads” row says why the other three differ in cost, and the cost is what pushes content from one into another.
The documentation is blunt about the first. CLAUDE.md content is treated as context, not enforced configuration, and for anything that must hold, it points to a PreToolUse hook. The same page recommends targeting under 200 lines per file and says an entry that is “a multi-step procedure or only matters for one part of the codebase” belongs in a skill.
Here is the whole decision as a sequence of questions, asked in order.
Must it hold even if the model
decides otherwise?
│
yes ─┴─ no
│ │
▼ ▼
HOOK Does it need a system the
model cannot reach today?
│
yes ─┴─ no
│ │
▼ ▼
MCP Is it needed every session?
(+ a │
skill) yes ─┴─ no
│ │
▼ ▼
CLAUDE.md SKILL
each answer to "no" hands the
content to a softer layerRead it top to bottom and the order is a statement of priorities. Enforcement comes first because it is the only layer that does not depend on the model agreeing. The skill is last because it is the default home for anything that is neither a guarantee, nor access, nor universal.
A skill is a description plus a body
The format is smaller than the ecosystem around it suggests. A skill is a directory with a SKILL.md file: YAML frontmatter, then Markdown instructions. Per the skills documentation, no frontmatter field is required. Only description is recommended, name defaults to the directory name, and unknown fields are ignored without an error.
Where it lives decides who gets it:
| Location | Path | Applies to |
|---|---|---|
| Personal | ~/.claude/skills/<name>/SKILL.md |
All your projects on this machine |
| Project | .claude/skills/<name>/SKILL.md |
Sessions in that repository |
| Nested | <subdir>/.claude/skills/<name>/SKILL.md |
Sessions started in or below that directory |
| Plugin | <plugin>/skills/<name>/SKILL.md |
Namespaced as plugin-name:skill-name |
If the same name exists at several levels, enterprise wins over personal and personal wins over project. Claude Code also watches the skill directories, so adding or editing a skill takes effect in the current session without a restart.
The loading model is the part that explains everything else. In a regular session, skill descriptions are loaded into context so Claude knows what exists, and the full content loads only when the skill is invoked, either by you typing /name or by Claude matching the description to your task. Once invoked, the rendered content enters the conversation as a single message and stays there. That is why a skill costs little until used, and why the description is the only part of it that is always present.
Frontmatter also changes who triggers it. disable-model-invocation: true keeps Claude from loading the skill on its own and keeps its description out of context, which the documentation recommends for skills with side effects. user-invocable: false does the opposite: Claude can load it, but it is hidden from the slash menu.
The description decides whether anything happens
The body of a skill is what you write carefully. The description is what determines whether the body is ever read.
The documentation says Claude matches your task against skill descriptions, and that if they are vague or overlap, Claude may load the wrong skill or miss one that would help. The description and when_to_use text is truncated at 1,536 characters in the listing, so the trigger belongs at the front.
A practitioner thread that circulated in September put the same point plainly. @claudeskills101 described the trigger as “a two line description that tells Claude when to use it, which is the whole magic of the system,” and advised naming the exact task it should fire on instead of something vague, then testing by asking for the task without mentioning the skill. That test is the right one. A skill you only ever invoke by name has not been tested as a skill.
Descriptions that work do two things in two sentences: say what the skill does, then say when to use it, in the words a request would actually contain. Here is the one from my article skill.
---
name: sawinyh-article-prep
description: Draft, gate, and ship a blog post for sawinyh.com (Astro content collection). Use when writing a new post, turning research or notes into an article, or checking a draft against house style.
---
It names the site, so it will not fire on someone else’s blog post. It lists the phrasings a request would use: writing a post, turning notes into an article, checking a draft. And the x-research skill opens with its trigger: “Use whenever content must be pulled from X/Twitter for any reason.” That is a description written for the request, not for a catalogue.
The failure runs the other way too. A description that is too broad fires on requests it should not touch, and that is what happened to me.
The Codex misfire was a description doing its job
When I asked for a “Codex review,” the agent kept reaching for a Codex plugin skill. That skill routes through a forwarder subagent and a companion script. I meant the codex CLI, called directly.
The plugin skill’s listed description is “Delegate investigation, an explicit fix request, or follow-up rescue work to the Codex rescue subagent.” Nothing in that is wrong. It names Codex and it names delegation, and my request named Codex and asked for a second opinion. The match was reasonable. It was also the wrong tool, and a better-written description on my side would not have helped, because the description was not mine.
The fix I made was a rule in my global CLAUDE.md saying that Codex means the CLI. That is advice, the thing this series argues against. It works because the situation is narrow and the wording is unambiguous, but it is still context the model can talk past.
The harness has tighter tools for skills you did not write, but they did not reach this one. The documentation describes skillOverrides in settings for hiding a skill without editing its file, and it also says plugin skills are not affected by it. This was a plugin skill. That left disabling the whole plugin, which I did not want, or a rule, and I chose the rule. What I can say is that a skill that triggers on the wrong request is a description problem, and the fix belongs next to the description, not in a sentence somewhere else that asks the model to remember.
A related failure: a guessed --model flag on the codex command failed outright with a 400, because that model was not available on the account type. That one has nothing to do with skills. It is here because it is the same shape: a confident guess about a system’s contents, with the system answering no.
MCP is access, and access is not know-how
The Search Console server I use is a clean example of the other boundary. It gives Claude access to queries, URL inspection and sitemaps. What it does not give is any sense of what to do with them, and as it turned out, not full permission either.
Its OAuth scope is read-only. sitemaps_submit returns “Permission denied” even though the account is the site owner. The account has the rights. The token does not. No amount of instruction in a skill or in CLAUDE.md changes that, because the limit is in what the server was granted, and it is exactly the kind of fact that a connection layer owns.
The note that the Google sitemap submission is blocked, and that the next session should not retry the call expecting a different result, lives in a memory file, not in a skill. A skill could carry it too. The point is that the server cannot: access says what is possible, and something else has to say what is worth trying.
This is Anthropic’s own framing of the pairing: MCP gives Claude purpose-built tools for an external system, with the connection and authentication handled by the server, and a skill gives Claude knowledge about how to use those tools effectively, such as your schema and query patterns. If you find yourself writing a skill that describes how to call an HTTP API by hand, that is an MCP server or a CLI wearing a skill’s clothes. If you find yourself writing an MCP server to hold your team’s conventions, it is the reverse.
@akshay_pachaar drew the same line in August when writing about a cross-vendor plugin package format: a skill is a folder of written instructions the agent reads when it needs them, and an MCP server is a small program that connects the agent to a tool, an API or a database. The two are packaged side by side in a plugin and fail separately, which is a reasonable summary of why you do not want to merge them.
The pair that works: a skill and the hook that guards it
My X research setup is the case I would copy. It has no MCP server at all. Access is a CLI, bird, which returns a post’s full text, author, timestamp and engagement counts as JSON. The know-how is a skill.
The reason it exists is that WebFetch and web search do not work on x.com: a real post URL returns HTTP 402 with no body. So the skill teaches the right path, and it encodes things that calling the CLI from memory gets wrong:
- A response that changes shape when a query paginates, so
jq 'length'reports 2 on a result that contains dozens of posts. - A search that is chronological only, so a bare keyword query returns noise unless you add an engagement floor.
- A hard cap on how many results a single search returns.
None of that is a fact about my project, so it does not belong in CLAUDE.md. None of it is access, so it does not belong in an MCP server. It is know-how that matters only on one kind of task. That is the skill’s job.
The skill alone is not enough. An agent that has never been told about the skill, or that decides WebFetch is faster, will try WebFetch on the URL first. So a PreToolUse hook, block-x-webfetch, refuses WebFetch on x.com. The hook stops the wrong path, and the skill teaches the right one. I wrote more about why the stopping half cannot be a sentence in the hooks post.
The skill’s failure mode is the other half of the table: skills fail when they never trigger, or when they duplicate access an MCP server should provide. Each covers the other’s gap, which is the reason to build them as a pair.
A script inside a skill
The article skill shows a different feature: a skill can carry code. The documentation describes supporting files, with SKILL.md as the overview and navigation and scripts alongside it that are executed rather than loaded. It recommends keeping SKILL.md under 500 lines and moving reference material into separate files.
Mine has a gate script. It checks the frontmatter contract, bans em dashes, flags sentences that start with a quantity, and checks that every number in the summary and FAQ appears in the body. Because the script runs through Bash and is not read into context, it costs nothing until it is called.
The skill tells Claude to run the script before shipping. The script is deterministic, so once it runs, the result is not a matter of interpretation. But nothing forces it to run. As I have set it up, no hook makes the run mandatory, so the check is exactly as reliable as Claude following the instruction. A gate script inside a skill belongs to the teaching layer. It is not enforcement.
That distinction tells you where to put a rule. If a check is worth running every time, wire it to an event. If it is worth running when a particular kind of task comes up, put the script in the skill that teaches that task.
Where each thing goes
Put it together and the sorting is mechanical.
- A fact true of the project in every session: build command, layout, a never-do-X line. CLAUDE.md, kept short.
- A procedure with steps, or reference material needed for one kind of task. A skill, with a description written for the request.
- A system Claude cannot reach, or an action it cannot take. An MCP server, scoped to what the task needs, and a skill if its use needs explaining.
- A rule that must hold regardless of what the model decides. A hook.
Content moves between them as you learn. The documentation’s own trigger list says to add a convention to CLAUDE.md after Claude gets it wrong twice, and to capture a playbook as a skill when you paste it into chat for the third time. That is the direction things travel: from nothing, to CLAUDE.md, to a skill, and for the few that cannot be allowed to fail, to a hook.
The limit of mine
Both of my project skills, the article skill and an SEO skill, live in a .claude directory that is gitignored. They exist on one machine. If that disk dies, or a collaborator clones the repo, the know-how is gone.
The documentation says to commit a project skill so your team gets it too. I have not, and the cost is real: the setup that produces these posts cannot be reproduced from the repo. A skill is the right home for this know-how and the wrong place to keep the only copy.
Deciding what belongs in the repository and what stays on the machine is still open for me.