How to use skills in Claude Code: writing the SKILL.md and when it beats MCP and CLAUDE.md
Your CLAUDE.md is 400 lines long and Claude ignores half of it.
The model isn't being stubborn. It's context physics: everything in CLAUDE.md lands in every session, on every task, competing for attention with what actually matters right now.
Skills fix this the right way. Knowledge only enters the context when the task asks for it.
Using skills in Claude Code means packaging a process into a folder with a SKILL.md file, which the agent only loads when the task matches its description. In this tutorial you'll learn how to use skills in Claude Code the way that works in production: the folder structure, the SKILL.md frontmatter, how progressive loading saves context, how to bundle scripts and references inside the skill, and how to test whether the agent triggers it. The live example is the set of skills that run this blog's editorial pipeline. This very post was written, reviewed and published by them. And at the end there's the decision matrix nobody hands you: when to use a skill, MCP, CLAUDE.md, a slash command or a subagent.
TL;DR
- What it is: a skill is a folder with a
SKILL.md(YAML frontmatter + Markdown instructions) and, optionally, scripts and reference files. Claude Code only loads thedescriptionat startup; the rest comes in when the skill is triggered. - Where it lives:
.claude/skills/<name>/SKILL.mdin the project, or~/.claude/skills/<name>/SKILL.mdfor all your projects. - Fields that matter:
nameanddescription. The Claude Code extras (disable-model-invocation,allowed-tools,context: fork,arguments) come in when you need fine-grained control. - Cost/Access: native Claude Code feature, nothing to install. The format follows the open Agent Skills standard.
- Useful link: official Claude Code skills documentation.
Context: why skills exist (and why your CLAUDE.md keeps growing)
Every Claude Code setup goes through the same phase.
You start with a lean CLAUDE.md. Then the deploy process shows up. Then the code review checklist. The step-by-step for creating migrations. The rules for the monthly report. It all goes into the same file, because it's the one place the agent "always reads".
The problem is exactly that "always". Deploy instructions do nothing for you when the task is fixing a test. They just take up space and dilute the model's attention on what matters. We covered this when Anthropic cut 80% of Claude Code's system prompt: whatever sits permanently in context has to be minimal and precise.
Skills flip the logic. According to the official docs, in a regular session only skill descriptions are loaded, so Claude knows what's available. The full content only loads when the skill is invoked. Supporting files, only when they're referenced.
Three layers:
- Always in context:
name+descriptionof each skill. - On invocation: the body of
SKILL.md, which enters the conversation as a single message and stays there for later turns. - On demand: the
references/files theSKILL.mdtells it to read, and thescripts/it tells it to run (scripts execute, they don't get loaded).
This blog's pipeline has eight skills, adding up to 1,265 lines of SKILL.md. If all of that lived in CLAUDE.md, every session would start with an entire editorial process manual loaded, even when the task is fixing a typo. As skills, the fixed cost is one description line per skill.
This is the kind of context architecture decision we put on the table at the Clã Beer and Code: harness, agents, RAG and evals built alongside people who are shipping AI to production, not just reading about it.
If you want the news side (the Agent Skills standard was donated to the Agentic AI Foundation on 10/22/2026), it's all in the post on what changes for your SKILL.md with the open standard. Here the focus is the how-to.
Prerequisites
- [ ] Claude Code installed and running in a project (if you're not using it yet, start with what Claude Code is and how it works)
- [ ] A process you repeat and currently explain in chat every time (deploy, review, release notes, report)
- [ ] Basic YAML and Markdown
- [ ] Optional: a script that's already part of that process (bash, Python, PHP, whatever)
Hands-on: how to create and use skills in Claude Code
Step 1: create the folder in the right place
The folder name becomes the command name. Claude Code looks for skills in a few scopes, according to the docs:
| scope | path | who sees it |
|---|---|---|
| Project | .claude/skills/<name>/SKILL.md |
anyone who opens this repo (commit it and the whole team gets it) |
| Personal | ~/.claude/skills/<name>/SKILL.md |
you, across all projects |
| Nested | <subdir>/.claude/skills/<name>/SKILL.md |
sessions that touch files in that subdirectory |
| Plugin | <plugin>/skills/<name>/SKILL.md |
whoever installs the plugin, as /plugin:name |
Rule of thumb: project process goes in .claude/skills and into git. Your personal preferences go in ~/.claude/skills.
This blog's pipeline is all project-level. The real structure looks like this:
.claude/skills/
├── cacar-news-jacking/
│ ├── SKILL.md
│ ├── references/
│ │ ├── grok-prompt.md
│ │ ├── output-template.md
│ │ └── sources.md
│ ├── scripts/
│ │ └── detect.sh
│ └── state/
├── criar-post-blog/
│ ├── SKILL.md
│ ├── references/
│ │ ├── audio-script.md
│ │ ├── brand-voice.md
│ │ ├── post-template.md
│ │ └── sources.md
│ ├── scripts/
│ │ ├── generate-audio.sh
│ │ └── generate-cover.sh
│ └── voice/
└── minerar-evergreen/
├── SKILL.md
└── references/
└── output-template.md
Notice that only SKILL.md is required. minerar-evergreen (the evergreen topic miner) has one reference and zero scripts. cacar-news-jacking (the news-jacking hunter) even has a state/ folder to keep snapshots between runs. The folder is yours: organize it the way the process needs.
Step 2: write the frontmatter (the part that matters most)
SKILL.md starts with YAML between --- markers. A detail that trips people up: Claude Code only reads the frontmatter if the opening --- is on the first line of the file. And if the YAML doesn't parse, the skill loads with every field empty, with no error in your face.
The real frontmatter of the skill that hunts hot topics for this blog (kept in its original Portuguese):
---
name: cacar-news-jacking
description: Caça temas de news-jacking para o blog Beer & Code (blog.beerandcode.com.br) — detecta notícias/lançamentos/dramas de IA em ascensão ANTES da SERP saturar e devolve candidatos de pauta ranqueados, já com títulos otimizados pra CTR. Cruza detecção em tempo real (Hacker News, OpenRouter, Polymarket, GitHub, Reddit, NewsAPI/GDELT, RSS dos labs + X/Twitter via Grok) com qualificação de SEO (DataForSEO + GSC). Acionar quando o usuário pedir "caçar tema", "o que tá quente", "tem notícia pra surfar", "achar tema de news-jacking", "tema quente de IA", "o que postar hoje" ou equivalente. Entrega candidatos ranqueados (não escreve nem publica — para isso use planejar-pauta e depois criar-post-blog).
---
That description has three parts, and all three are on purpose:
- What it does, in the first sentence.
- When to trigger, using the phrases I actually type ("what's hot", "what should I post today").
- What it doesn't do, pointing to the neighboring skill. Without that line, Claude tries to write the post inside the hunting skill.
The description is the router. It's the only thing the model sees before deciding whether to load the skill. Vague description, skill that never fires. Overly generic description, skill that fires on everything.
Two limits the Claude Code docs spell out: the combined description + when_to_use text is truncated at 1,536 characters in the listing, and the whole skill listing has a budget of 1% of the context window. With lots of skills, Claude Code starts dropping descriptions, starting with the skills you use least. So: main trigger up front, process detail in the body.
Fields worth knowing:
| field | what it's for |
|---|---|
name |
command name in the / menu. Default: folder name |
description |
what it does and when to use it. The field that decides triggering |
when_to_use |
extra triggers, appended to the description in the listing |
disable-model-invocation: true |
only you trigger it, via /name. Claude won't load it on its own |
user-invocable: false |
hidden from the / menu. Only Claude triggers it |
allowed-tools |
tools allowed without a permission prompt while the skill runs |
arguments / argument-hint |
named arguments and autocomplete hint |
context: fork + agent |
runs the skill in an isolated subagent |
model, effort |
swaps model or effort for the skill's turn |
paths |
only auto-activates when the work involves files matching the glob |
Two warnings. A misspelled field is silently ignored, so disable-model-invokation (with a k) just does nothing. And outside Claude Code (claude.ai uploads, Skills API) only name, description, license, compatibility, metadata and allowed-tools are allowed; any extra field is a hard error. If the skill is going to travel, stick to the core.
Step 3: write the body as a process, not a prompt
After the frontmatter comes plain Markdown. What works is treating the body as a runbook: inputs, numbered steps, exit criteria. The docs recommend keeping SKILL.md under 500 lines; the largest skill in this pipeline has 244.
A real excerpt from minerar-evergreen. Notice it's not "be creative and find good topics". It's a business rule with a date and a reason (translated here):
## PAIN gate (top of funnel) — apply before anything else
What works as evergreen on this blog is **low-awareness pain**:
the searcher hasn't picked a product yet, but has a concrete problem.
Product-centered topics ("what is Codex", "how to install Claude Code")
were rejected on 09/13/2026: they're disguised news-jacking and die when
the tool changes.
- **Seed with problem verbs**, not tool names...
- **Per-candidate filter**: "is this a pain or a product?"
That's the difference between a good skill and a decorative one. It carries decisions the team already made, with the why. The model doesn't need to rediscover that product topics die fast. Someone already paid for that lesson.
Another thing the docs warn about, and it's gold: after context compaction, Claude Code keeps only the first 5,000 tokens of each invoked skill (with a combined budget of 25,000). So critical rules go at the top of SKILL.md, not the bottom. The skills on this blog open with a "Project constants (don't ask)" section for exactly that reason.
Step 4: offload weight from SKILL.md into references and scripts
This is progressive loading working in your favor.
criar-post-blog (the post-writing skill) needs the brand voice guide (almost 8 KB), the post template, the audio script rules. None of that is in SKILL.md. It just points to it:
### 3. Writing
Before writing, **read `references/brand-voice.md`**. Tone is the
blog's differentiator — it's non-negotiable.
The voice guide only enters the context at the writing step. During research, it costs nothing.
Scripts are even better, because the code runs and only the output comes back into context. cacar-news-jacking hits Hacker News, OpenRouter, Polymarket, GitHub and GDELT in a 9.5 KB detect.sh. The SKILL.md only says how to call it:
bash .claude/skills/cacar-news-jacking/scripts/detect.sh # foco default
bash .claude/skills/cacar-news-jacking/scripts/detect.sh -q "claude OR anthropic"
Everything deterministic (hitting APIs, building JSON, converting audio, uploading files to storage) goes into a script. The model orchestrates and interprets. Don't burn tokens asking the LLM to do curl's job.
An honest confession: the skills here use paths relative to the repo root, because Claude Code always runs from there. If the skill might be installed in another scope, use the ${CLAUDE_SKILL_DIR} variable, which resolves to the skill's directory whether it's project, personal or plugin:
python3 ${CLAUDE_SKILL_DIR}/scripts/visualize.py .
Step 5: arguments and dynamic context
Now a brand-new skill, from scratch, to lock in the rest. Release notes from git, with an argument and injected context:
---
name: release-notes
description: Gera release notes em pt-BR a partir dos commits desde a última tag. Use quando o usuário pedir "release notes", "changelog da versão" ou "o que mudou desde a última release".
argument-hint: "[versão]"
arguments: versao
allowed-tools:
- Bash(git log *)
---
# Release notes da versão $versao
## Commits recentes (a tag aparece entre parênteses)
!`git log --oneline --no-merges --decorate -50`
## Instruções
1. Considere só os commits acima da última tag e agrupe em: Novidades, Correções, Interno.
2. Ignore commits de chore/ci, a menos que mudem comportamento visível.
3. Siga o formato de `references/formato.md`.
4. Termine com uma linha de "Breaking changes", mesmo que seja "nenhuma".
(The skill is written in Portuguese because it generates Portuguese release notes; the mechanics are language-agnostic.)
Three mechanisms in that file:
$versaocomes from theargumentsfield./release-notes 2.4.0fills in the value. There's also$ARGUMENTS(everything typed) and positional$0,$1.!`command`runs before Claude sees the skill and pastes the output in place. The model gets the commits ready to go, without spending a tool call. If the command fails, the whole invocation aborts, and the command goes through permission rules (hence theallowed-tools).allowed-toolsallows only those commands without a prompt during the skill's turn. The grant ends with your next message.
Step 6: test whether the agent triggers it
A skill that never fires is a dead file. The test routine:
- Check it loaded. Open the
/skillsmenu or ask "What skills are available?" in the session. If it doesn't show up, the problem is the file, not the description. - Test direct invocation.
/release-notes 2.4.0. If it works directly but not automatically, the problem is the description. - Test auto-triggering with the real phrase. Ask the way you'd ask on a normal day: "what changed since the last release?". If it doesn't fire, put that exact phrase in the description.
- Test for false positives. Ask something adjacent ("summarize the last commit") and see if the skill fires when it shouldn't. If it does, the description is too broad.
- Validate the YAML.
claude plugin validate .claude/skillsfindsSKILL.mdfiles with broken frontmatter (Claude Code v2.1.233+).claude --debugshows parse errors.
If you want numbers instead of vibes, the skill-creator plugin generates prompts that should and shouldn't trigger the skill, measures the hit rate and suggests description tweaks. It's the same eval mindset you'd apply to any AI feature: without measuring, you're guessing.
A trap I learned the hard way: a skill can trigger correctly and Claude can stop following one of its rules a few turns later. criar-post-blog has a bold warning to run the audio script in blocking mode, never in the background, because in a headless session the agent would end the turn waiting for the mp3 and the post never got published. The docs are blunt about this: a rule that must hold every time isn't a skill's job, it's a hook's job, which runs every time the event happens, whether or not the model follows the skill. A skill is guidance applied with judgment. A hook is a guarantee.
A tutorial shows you the way — in the Clã you build alongside us. A live class every week, real AI Engineering projects, next to people already in production.
Join the ClãSkill vs MCP vs CLAUDE.md vs slash command vs subagent
This is the decision that sticks. Five pieces, five different problems:
| piece | what it is | enters context | use it when |
|---|---|---|---|
| CLAUDE.md | fixed project context | always, every session | rules that apply to any task: stack, conventions, test commands, what never to do |
| Skill | packaged procedural knowledge | only the description; the rest on demand | a multi-step process that only matters for certain tasks |
| MCP | protocol connecting external tools and data | tool definitions | the agent needs to act on a system: API, database, CMS, SaaS |
| Slash command | shortcut you trigger | when you type it | now it's a skill: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create the same /deploy |
| Subagent | another agent, with its own context | only the result comes back | an isolatable task that pollutes context or fits a cheaper model |
This blog's pipeline uses all five, each in its place:
- CLAUDE.md holds the rule that always applies: never touch production without explicit authorization. That can't depend on a skill being triggered.
- Skills hold the processes: hunting topics, mining evergreen keywords, writing, SEO review.
- MCP is the agent's hand on the CMS.
criar-post-blogknows how to publish; thebeer-and-code-blogMCP is what publishes (create-post,upload-image,upsert-post-translation). The skill teaches process. MCP gives capability. Neither replaces the other, and the best skills usually orchestrate several MCP tools. If you want to build the protocol side, there's the MCP server from scratch tutorial. - Slash commands became an interface detail. Every project skill is already a
/name. If you want only a human to trigger it (deploy, for example), usedisable-model-invocation: trueand you're done. - Subagents take the part that doesn't need the expensive model. Rewriting the post into a narration script is pure text transformation, so the skill delegates to the
roteiro-narracaosubagent, which runs on Haiku:
---
name: roteiro-narracao
description: Reescreve o body em Markdown de um post do blog Beer & Code numa versão narrável (texto plano UTF-8) pronta para TTS. Use SEMPRE no passo 5.1 da skill `criar-post-blog`...
model: haiku
---
Notice subagents also have a description: the routing mechanism is the same. The difference is that a skill injects instructions into your context, while a subagent runs in a separate context and only hands back the result. And there's a middle ground: context: fork makes the skill itself run as a subagent.
The short heuristic:
- Applies to every task? CLAUDE.md.
- Applies to one kind of task and it's a process? Skill.
- Needs to talk to an external system? MCP (called from inside the skill, if there's a process around it).
- Only a human may trigger it? Skill with
disable-model-invocation: true. - Generates lots of context noise or fits a cheap model? Subagent.
- Must happen every time, no exceptions? Hook.
To see hooks and MCP side by side with more config detail, the post on the anatomy of a productive harness complements this one.
Limitations and gotchas
- Triggering is probabilistic. The description raises the odds, it doesn't guarantee anything. Critical processes deserve explicit invocation (
/name) or a hook. - An invoked skill isn't re-read. Claude Code injects the content once and doesn't go back to the file in later turns. Edited
SKILL.mdmid-session? Invoke it again. Same after compaction. - The description budget is shared. Too many skills installed, descriptions get cut, triggering gets worse without you noticing.
/doctorshows the listing's cost; turn off skills you don't use. - A skill with scripts is code running with your permissions. Read third-party skills before installing them, just like a dependency. It's the same vector as the "clean" repository that gets Claude Code to install malware.
allowed-toolsisn't a sandbox. It skips permission prompts, it doesn't restrict what else can run. To block things, use deny rules in permissions.- Secrets don't live in the skill. Credentials go in
.envand the script reads from there. The skill folder goes into git.
Quick FAQ
What's the difference between a skill and CLAUDE.md? CLAUDE.md loads in full every session. A skill only takes up its description line until it's triggered. Project-wide rules stay in CLAUDE.md; processes that only matter for certain tasks become skills. If your CLAUDE.md has a "how to do X" section, that section is probably a skill waiting to be born.
Does a skill replace MCP? No. MCP gives the agent the ability to act on external systems: APIs, databases, CMSs. The skill teaches the process, and can use MCP tools along the way. In this blog's pipeline, the post-creation skill knows the order of the steps; the blog's MCP is what actually creates the post. To understand the protocol, see what MCP is.
Do my slash commands in .claude/commands/ stop working?
No. The official docs say custom commands have been merged into skills and files in .claude/commands/ keep working. If a skill and a command share a name, the skill wins. For new work, prefer skills, since they support supporting files in the folder.
Why doesn't my skill trigger?
In order: check that it shows up in /skills; test /name directly; if direct works, rewrite the description with the words you use when asking. Broken YAML also kills auto-triggering with no visible error; claude plugin validate .claude/skills catches it. Details in the official troubleshooting.
Conclusion
A skill isn't a saved prompt. It's procedural knowledge with on-demand loading: the description routes, SKILL.md instructs, references and scripts carry the weight only when the task calls for them.
The way to start is to look at your CLAUDE.md and hunt for sections like "when doing X, follow these steps". Each one is a skill. Move it, write a description with the phrases you actually type, test the triggering, and watch your CLAUDE.md fit back into the model's head.
The natural next step is combining skills with the rest of the harness. If your agent still stalls on long tasks, the problem is probably in another piece: the post on harness engineering shows where to look.
{AI Engineer} — apaixonado por Laravel, arquitetura de software e construir produtos com impacto. Compartilho aqui tutoriais, descobertas e reflexões sobre o dia a dia de engenharia.
There is no shortage of content. What is missing is someone to untangle it: what matters now is how to implement it the right way. In the Clã you get that live, every week, with people who have already filtered out the noise.
Join the Clã