DEV Community

Rulestack
Rulestack

Posted on

SKILL.md: how to write a Claude Code skill that actually triggers (format + template)

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:

  1. 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.
  2. 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 as ARGUMENTS: <value> instead.)
  • $0, $1 — positional access, shorthand for $ARGUMENTS[0], $ARGUMENTS[1].
  • Named arguments — declare arguments: [issue, branch] in frontmatter and $issue / $branch expand 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: true and stop fighting the matcher.
  • You can't tell if it loaded: invoke it explicitly with /skill-name once. 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.

The lean starter (added by request)

A reader asked for the leanest possible starting point — the version you paste in before you know what the skill will become:

---
name: my-skill
description: Use when I say "do X", "run X", or ask to X a file
---

# My skill

Steps:
1. ...
2. ...

For edge cases, read [notes.md](notes.md).
Enter fullscreen mode Exit fullscreen mode

That is genuinely enough to trigger. Add the rest only when it earns its place:

  • disable-model-invocation: true — once you notice it firing when you didn't ask
  • arguments — once you find yourself pasting the same values in by hand
  • supporting files — once SKILL.md drifts past a hundred lines of edge cases
  • context: fork — once the skill's output starts flooding your main session

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.

Top comments (9)

Collapse
 
mayank609 profile image
Mayank Bansal

Curious if you've experimented with measuring trigger precision/recall. Once you have dozens of skills, it feels like the challenge shifts from writing good instructions to making sure the right skill activates at the right time without overlap

Collapse
 
rulestack profile image
Rulestack

Great question — I haven't built a real precision/recall harness, just an informal split between false positives (a skill firing in an unrelated chat, which shows up as an unexplained slowdown) and false negatives (having to invoke manually). Past a dozen skills it's the description overlap that quietly hurts recall, so the lever that's helped me most is disambiguating them — distinct 'Use when...' triggers and explicitly scoping out neighbors — rather than polishing the instructions. If you ever log which skill fires per turn, I'd genuinely love to hear what the real numbers look like.

Collapse
 
rulestack profile image
Rulestack

Agreed — past a couple dozen skills the question stops being "is my description good" and becomes "did my description survive". Two things Claude Code already surfaces helped me more than rewriting prose: /doctor gives an estimate of the skill listing's context cost and names its biggest contributors, and the Skills row in /context reports the listing size after the budget is applied, so it matches what the model actually received rather than what you wrote. There's also a per-entry cap of 1,536 characters on description + when_to_use that truncates silently, which is worth ruling out before blaming the matcher. None of that is a precision/recall harness, but it turns "why didn't it fire" from guesswork into two numbers you can watch as the library grows. Runtime visibility here is genuinely under-served, so it's a good problem to be working on.

Collapse
 
ggle_in profile image
HARD IN SOFT OUT

My CLAUDE.md started as a gentle reminder about coding standards. Two months later, it was a 400-line manifesto containing project history, deployment rituals, and what I had for breakfast on Tuesdays. Skills were supposed to save me from that, but my first skill had a description so vague it might as well have been "does stuff." It never triggered once. I am pretty sure it is still sitting there, ignored, like that one coworker who keeps offering to help but never gets invited to meetings.

This is exactly the guide I needed when I first tried to write skills. The two-stage loading mental model is the key insight that most people miss—the description is not documentation, it is bait. And the disable-model-invocation: true tip is gold. I have definitely written action-oriented skills that Claude helpfully invoked at exactly the wrong time, like trying to deploy to production while I was just asking about the build process.

One thing I would add for people testing skills: after you write one, explicitly test it in a fresh session with the same phrasing you expect users to use. I have caught so many "never fires" issues just by typing "can you [thing]" into a clean chat and watching whether the skill loads. It is the cheapest way to validate your description without waiting for it to fail in production.

Also, since you mentioned the 500-line rule for SKILL.md, I would love to see a minimal template that just has the frontmatter and a brief navigation. Something that says "here is the lean version, here is what you add when it grows." That way, even people who overthink the format can get started without staring at a blank file for 20 minutes.

Anyway, this is going straight into my team's onboarding docs. Thanks for writing the guide that the official docs assume you already know.

Collapse
 
rulestack profile image
Rulestack

The 400-line manifesto arc is painfully real — mine drifted the same way, and your clean-chat test is the cheapest "never fires" check I've seen. The lean starter was a great call too — I've just added a minimal frontmatter-plus-navigation section with "add this when it grows" notes, so it's in there now. Thanks — glad it's heading into your onboarding docs.

Collapse
 
alexshev profile image
Alex Shev

The trigger behavior is the part people underestimate. A skill is only useful if the agent can recognize when to load it, and the format has to make that decision easier than improvising from memory.

Collapse
 
rulestack profile image
Rulestack

Agreed — and it's why the description field ends up doing most of the work. Writing it as the user's likely words ("use when the user says X") rather than a self-summary made the biggest difference for me: the agent is matching against the conversation it sees, not the skill's intent.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.