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.

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 (1)

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.