Claude Code skills solve a problem every heavy user eventually hits: you keep pasting the same checklist, procedure, or house rules into chat, or a section of your CLAUDE.md has quietly grown into a manual the model half-reads on every request. A skill packages those instructions as a file that the agent loads on demand: you can invoke it with a slash command, or Claude can load it itself when it decides the skill is relevant.
This guide covers how to write a Claude Code Skill from scratch: the exact SKILL.md format, the frontmatter fields that matter, why the description field decides whether your skill ever fires, and the mistakes that turn skills into dead weight. Every mechanical claim below comes from Anthropic's official Claude Code docs; the quality guidance comes from what we learned building PromptArch's free Claude Code skill generator and context-file linter.
What a skill is, and when to reach for one
A skill is a directory containing a SKILL.md file. The file has two parts: YAML frontmatter between --- markers that tells Claude when to use the skill, and a markdown body with the instructions Claude follows when the skill runs. The directory name becomes the command you type (.claude/skills/migration-review/SKILL.md gives you /migration-review), and the description helps Claude decide when to load it on its own.
The decision between a skill and other configuration surfaces comes down to loading behavior:
- CLAUDE.md is always-on. Every line of it sits in context on every request, whether it is relevant or not. It should hold durable facts: architecture, conventions, commands, constraints. If you want a starting point, PromptArch has a free CLAUDE.md generator.
- A skill is on-demand. Only its short description is always in context; the body loads when the skill is invoked. Anthropic's own rule of thumb: create a skill when you keep pasting the same instructions into chat, or when a section of CLAUDE.md has grown into a procedure rather than a fact. Long reference material in a skill costs almost nothing until you need it.
- Slash commands are now skills. Custom commands were merged into the skills system: a file at
.claude/commands/deploy.mdand a skill at.claude/skills/deploy/SKILL.mdboth create/deployand work the same way. Skills are the recommended form because they add a directory for supporting files, frontmatter to control who invokes them, and automatic model-triggered loading.
Where you store the skill decides its scope: ~/.claude/skills/<name>/SKILL.md is personal (all your projects), .claude/skills/<name>/SKILL.md is project-level (committed to the repo, shared with your team), and plugins can bundle skills under their own namespace.
Anatomy: a complete SKILL.md
Here is a realistic project skill that reviews new database migrations. Save it to .claude/skills/migration-review/SKILL.md:
---
name: migration-review
description: Reviews a SQL migration for destructive operations, lock risk,
and rollback safety. Use when the user adds or edits a migration file,
asks to review a migration, or mentions ALTER TABLE, DROP COLUMN,
backfills, or "is this migration safe".
allowed-tools: Read Grep
---
# Migration review
Review the migration $ARGUMENTS against this checklist:
1. Flag destructive operations (DROP TABLE, DROP COLUMN, TRUNCATE) and
confirm a backup or archival step exists before them.
2. Check long-running statements: adding a NOT NULL column with a default
on a large table, or an index built without CONCURRENTLY, locks writes.
3. Verify new foreign keys have a supporting index on the referencing side.
4. Confirm the migration is reversible, or that the PR notes why it is not.
5. Check RLS: any new table needs policies before it ships, because our
API reads it through the anon client.
Report findings as a short list ordered by severity. If everything passes,
say so in one line.
The pieces:
- Frontmatter. All fields are optional; only
descriptionis recommended, because it is what Claude reads to decide when to apply the skill.nameis just the display label in listings (the command name comes from the directory).allowed-toolspre-approves tools for the turn that invokes the skill so Claude does not stop to ask permission. - Body. Plain markdown instructions.
$ARGUMENTSis replaced with whatever follows the skill name when you type/migration-review supabase/migrations/039_foo.sql; if the placeholder is absent, Claude Code appends your input to the end instead. - Useful extras.
disable-model-invocation: truemakes the skill manual-only (right for/deploy-style workflows with side effects).user-invocable: falsehides it from the/menu, for background knowledge that is not a meaningful action.context: forkruns the skill in an isolated subagent. And a line like!followed by a backticked shell command runs before Claude sees the content, injecting live output (a diff, a file list) into the prompt.
The description is the make-or-break field
Here is the mental model that separates skills that fire from skills that rot: the description is a router, and Claude is the routing engine. In a normal session, Claude sees a listing of every skill's name and description. When your request comes in, it matches your words against those descriptions and decides which skill, if any, to load. The body of your skill, however good, is invisible at routing time.
That has three practical consequences:
- Say what the skill does AND when to use it. "Reviews SQL migrations" is half a description. The trigger half ("use when the user adds a migration file, asks to review a migration, or mentions ALTER TABLE...") is what actually gets matched.
- Cover the phrases people actually type. Include the vocabulary of the request, not the vocabulary of the solution. Users say "is this migration safe", not "perform lock-risk analysis". If the description misses their words, the skill never triggers, and you will wrongly conclude skills do not work.
- Put the key use case first. The combined
descriptionpluswhen_to_usetext is truncated at 1,536 characters in the skill listing, and with many skills installed the listing itself has a context budget, so front-load the sentence that matters. The optionalwhen_to_usefield exists precisely for extended trigger guidance: example requests and phrasings that should activate the skill.
One caveat: setting disable-model-invocation: true removes the description from context entirely. That is the point (Claude cannot trigger it), but it means all this routing advice applies only to skills Claude is allowed to invoke.
Progressive disclosure: keep the body lean
Skills are cheap until they load. Descriptions are always in context; the full body enters the conversation only when the skill is invoked. But once invoked, the rendered body stays in context for the rest of the session, so every line is a recurring token cost. Two rules follow:
- State what to do, not why. Apply the same conciseness test you would to CLAUDE.md. Anthropic's tip: keep SKILL.md under 500 lines.
- Push bulk into supporting files. A skill directory can hold
reference.md,examples.md, templates, or executable scripts alongside SKILL.md. Reference them from the body ("for the full API details, see reference.md") and Claude reads them only when needed. That is the second layer of progressive disclosure: description always, body on invoke, supporting files on demand.
What belongs in a skill, and what does not
Good skill material: multi-step workflows (release, deploy, triage), review checklists, repo-specific procedures ("how we write migrations", "how we cut a changelog"), and deep reference material behind a thin SKILL.md. In short: procedures and playbooks.
Poor skill material: durable facts about the codebase (that is CLAUDE.md), things you want enforced deterministically on every edit (that is hooks), permission policy (that is settings), and one-off prompts you will never reuse. The most common misplacement is duplication: the same rules living in both CLAUDE.md and a skill. They drift apart, and you pay for the CLAUDE.md copy on every single request.
Common mistakes
- Vague descriptions that never trigger. "Helps with database stuff" routes nothing. This is the number one reason skills sit unused.
- Skills that duplicate CLAUDE.md. If it is a fact the agent needs constantly, it belongs in CLAUDE.md once. If it is a procedure, move it out of CLAUDE.md entirely and into the skill.
- Giant bodies. A 900-line SKILL.md defeats progressive disclosure; after one invocation it is a permanent tax on the session.
- Auto-triggerable side effects. If a skill deploys, commits, or messages people, add
disable-model-invocation: true. You want to type/deploydeliberately, not have Claude decide your code looks ready. - Anti-patterns inside the body. Skill bodies are agent instructions, so the same research-backed rules that apply to CLAUDE.md apply here: emphasis inflation (walls of MUST/NEVER/CRITICAL), hedged "try to... if possible" phrasing that reads as optional, and bare prohibitions with no reason or alternative all measurably degrade current models. Our linter flags these (
antipattern/emphasis-inflation,antipattern/weak-language,antipattern/bare-prohibition, and about 30 more); paste your skill body into lint for a free offline check, and see the full rule list with sources.
Test the trigger, then the output
Seeing a skill trigger tells you Claude found it, not that it did what you intended, so measure the two separately:
- Trigger reliability. In a fresh session (leftover authoring context will mask gaps), type a few realistic requests that should activate the skill and a few that should not. If it under-triggers, add the missing phrases to the description or
when_to_use. If it over-triggers, make the description more specific or go manual-only. - Direct invocation as a control.
/skill-namealways works for user-invocable skills, and asking "What skills are available?" confirms the skill is registered. If/skill-nameworks but the frontmatter seems ignored, malformed YAML is the usual culprit: Claude Code then loads the body with empty metadata, andclaude --debugshows the parse error. - Output quality. Run the same prompts with the skill available and with it disabled, and compare. Anthropic's
skill-creatorplugin automates this loop with stored test cases, isolated runs, pass/fail grading, and description tuning.
Iterate on the description first. It is the highest-leverage line in the file.
Generate one in two minutes
You can write a SKILL.md by hand with everything above. Or you can have the structure, frontmatter, trigger-oriented description, and a lint-clean body generated for you: PromptArch's free Claude Code skill generator walks you through name, description, triggers, tool permissions, and workflow steps, and outputs a ready-to-save SKILL.md. The first one is free, no signup required. Then run it through lint and ship it to your repo's .claude/skills/.