Claude Code skills answer a specific pain: you keep pasting the same checklist into chat, or a section of your CLAUDE.md has quietly grown from facts about the project into a multi-step procedure. The official guidance is exactly that — when instructions become a procedure you re-explain, move them into a skill. Unlike CLAUDE.md content, a skill's body loads only when it's used, so a long runbook costs you almost nothing until the moment you need it.
This post covers the SKILL.md format end to end: the frontmatter fields, how triggering actually works, arguments, supporting files, and a template you can copy.
The mental model: two-stage loading
Everything about writing a good skill follows from one design fact:
- The description is always visible. Claude sees your skill's name and description in every session, and uses them to decide when the skill is relevant.
-
The body loads only on use. The instructions below the frontmatter enter context only when the skill fires (or when you invoke it with
/skill-name).
So the description is not documentation — it's the trigger. The body is not a summary — it's the procedure. Most broken skills I've seen get these backwards: a vague description ("Helps with releases") that never matches anything, and a body that's a one-liner.
A minimal working skill
A skill is a directory with a SKILL.md file. In a project:
.claude/skills/release-notes/SKILL.md
Enter fullscreen mode Exit fullscreen mode
(Personal skills live under ~/.claude/skills/ and follow you across projects.)
---
name: release-notes
description: "Draft release notes from merged PRs. Use when the user"
asks for release notes, a changelog entry, or "what shipped".
---
Collect merged PRs since the last git tag, then write release notes.
1. Run `git describe --tags --abbrev=0` to find the last tag.
2. List merges since then: `git log <tag>..HEAD --merges --oneline`.
3. Group changes into Added / Fixed / Changed.
4. Write entries as user-facing outcomes, not commit messages.
5. Show the draft before writing to CHANGELOG.md.
Enter fullscreen mode Exit fullscreen mode
That's a complete, working skill. Type /release-notes to run it directly, or just ask "can you draft release notes?" and let the description do its job.
If you've used custom commands before: commands and skills are now the same thing. A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and behave the same way. Skills just add optional extras — a directory for supporting files and frontmatter to control invocation.
Frontmatter: four fields you'll actually use
All fields are optional. Only description is recommended — without it, Claude has nothing to match against, and the skill is effectively manual-only.
---
name: my-skill
description: What this skill does
disable-model-invocation: true
allowed-tools: Read Grep
---
Enter fullscreen mode Exit fullscreen mode
-
name— the command name (/my-skill). Defaults to the directory name, so most skills omit it. -
description— the trigger. Write it for the matcher, not for humans browsing. Include the literal phrases people say: "Use when the user asks to X, Y, or mentions Z." -
disable-model-invocation: true— Claude can never fire this on its own; only you can, via/name. Use it for anything with side effects you want a human to initiate: deploys, commits, publishing, sending messages. This is the difference between a reference skill (let Claude pull it in when relevant) and a task skill (you pull the trigger). -
allowed-tools— pre-approve the tools the skill needs (e.g.Read Grep), so a read-only helper doesn't stop to ask permission mid-run.
That last pairing is worth internalizing: knowledge skills → model-invoked; action skills → slash-only. The frontmatter is where you encode that decision, and it's the single biggest safety lever in the format.
Arguments
Skills take arguments when invoked as commands, with real substitution syntax:
-
$ARGUMENTS— everything you passed. (If it doesn't appear in the body, arguments are appended asARGUMENTS: <value>instead.) -
$0,$1— positional access, shorthand for$ARGUMENTS[0],$ARGUMENTS[1]. -
Named arguments — declare
arguments: [issue, branch]in frontmatter and$issue/$branchexpand by position. Self-documenting, so I'd default to this for anything with two or more parameters.
So /fix-issue 1423 hotfix/login with arguments: [issue, branch] gives you a body that reads like prose: "Fix issue $issue on branch $branch."
Supporting files: the 500-line rule
Official guidance: keep SKILL.md under 500 lines and move detailed reference material into separate files in the skill directory:
my-skill/
├── SKILL.md (required — overview and navigation)
├── reference.md (detailed API docs — loaded when needed)
├── examples.md (usage examples — loaded when needed)
└── scripts/
└── helper.py (executed, not loaded)
Enter fullscreen mode Exit fullscreen mode
The important part is the navigation: link each file from SKILL.md with a note about what it contains and when to read it —
## Additional resources
- For complete API details, see [reference.md](reference.md)
- For usage examples, see [examples.md](examples.md)
Enter fullscreen mode Exit fullscreen mode
This is the same two-stage trick again, one level down. SKILL.md stays a lean table of contents; the 2,000-line API reference costs nothing until Claude actually follows the link. Scripts are even cheaper — they get executed, not read into context.
When a skill won't trigger (or triggers too much)
Almost every triggering problem is a description problem:
- Never fires: the description describes the domain ("Release management helpers") instead of the moment ("Use when the user asks for release notes, a changelog, or what shipped"). Rewrite it around the phrases users actually say. Front-load them — long descriptions get truncated in listings.
-
Fires too often: the description is too broad ("Use for anything git-related"). Narrow the trigger, or if it's a task skill that should never auto-fire anyway, set
disable-model-invocation: trueand stop fighting the matcher. -
You can't tell if it loaded: invoke it explicitly with
/skill-nameonce. If it works manually but never automatically, it's the description. It's almost always the description.
One more option worth knowing: skills can run in their own subagent context (context: fork), which keeps a noisy multi-step procedure from flooding your main session. Useful for research-style skills that read a lot but should report back a summary.
Portability note
SKILL.md follows the Agent Skills open standard, and the same format is now read by several coding agents (Claude Code extends it with invocation control, subagent execution, and dynamic context injection). Writing your team runbooks in this format is a reasonably safe bet even if your tool mix changes later.
I maintain Rulestack, a set of focused rule and skill packs for Claude Code, Cursor, and Codex — including a full Claude Code skill-authoring pack.
Daily AI-coding-agent tips on Bluesky: follow @ai-shop.bsky.social.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.