Claude Code Skills·Claude Skills·The open SKILL.md registry for Claude
HomeLearn › Writing a SKILL.md File: A Practical Walkthrough

Writing a SKILL.md File: A Practical Walkthrough

Published 31 May 2026 · 14 min read · By a long-time Claude Code practitioner

This is the kitchen-table version. By the end of it you'll have a working SKILL.md file on disk, Claude Code will be loading it at session start, and you'll know how to iterate on it without losing your mind. We're going to write a real skill from the first five lines up, then dig into the parts that separate a skill Claude actually invokes from one that sits in ~/.claude/skills/ collecting dust.

If you want the conceptual map of what skills are and how they fit into the broader agent toolkit, read how to write a Claude Code skill first. If you want the precise field reference, that lives at claude-code-skill-frontmatter-fields. This page is the one you keep open in a second tab while you're typing.

In this guide

The five-line skill that actually works

Let's start with the smallest thing that's actually a skill. Open a terminal:

mkdir -p ~/.claude/skills/changelog-bump
cd ~/.claude/skills/changelog-bump
$EDITOR SKILL.md

Paste this and save:

---
name: changelog-bump
description: When the user asks to add an entry to CHANGELOG.md or bump the changelog, prepend a new dated entry under an Unreleased section. Use today's date in YYYY-MM-DD format.
---

When invoked, open CHANGELOG.md in the project root, find or create the `## Unreleased` section, and prepend the user's described change under it as a bullet. If the user didn't specify the change, ask what to log.

That's a working skill. Five lines of frontmatter plus a one-line body. Restart Claude Code (close the session, open a fresh one — we'll get to why in a minute), then in the new session type: "add a changelog entry that we switched to PostgreSQL".

Claude Code reads the description, decides this skill matches, and follows the instructions in the body. No tool registration, no JSON manifest, no compilation step. The file is the program.

A few things to notice before we expand on each one:

If you got the directory name wrong, or the YAML frontmatter is malformed (missing --- delimiters, bad indentation, tabs instead of spaces), Claude Code silently skips the skill. There's no error popup. So if your skill isn't firing, the first thing to check is whether the file even loaded.

Frontmatter anatomy: name and description

The frontmatter is the block between the two --- lines at the top of the file. It's YAML. Two fields are required:

name (required)

The slug Claude Code uses to identify the skill internally. It must match the directory name exactly. Convention is lowercase kebab-case: code-review, aws-lambda-deploy, jira-ticket-from-error. Don't use spaces, underscores, or capital letters. If your directory is ./My_Cool_Skill/, fix it before going further.

The name is not user-facing in any conversational way — Claude doesn't say "I'll use the code-review skill now." But it appears in logs, in the skill index, and if you ever publish the skill to a catalog or share it with a teammate, the slug is its identity. Pick something descriptive. helper is bad. postgres-migration-helper is good.

description (required)

This is the single most important line in your skill. Claude reads every loaded skill's description at the start of every session, and uses those descriptions to decide which skill to invoke when you make a request. The description is the trigger.

Think of it as a precondition statement. Not "this skill reviews code" but "when the user asks to review, audit, or critique a file or PR, do a structured code review covering correctness, security, and clarity." The shape is: when X happens, do Y. The more specific the trigger phrases, the better Claude's matching gets.

Two failure modes I see constantly:

  1. Too vague. "A skill for git operations" matches almost any request involving git, including ones it shouldn't handle. Claude either invokes it inappropriately or learns to ignore it entirely because the signal is noisy.
  2. Too narrow. "Use when the user types exactly 'review this PR'" misses every paraphrase. Real users say "can you look this over," "thoughts on this diff," "what would you change here" — and the skill never fires.

Optional fields you'll meet soon

You can add allowed-tools, model, argument-hint, user-invokable, license, author, and a few others. None of them are required to ship. They become useful once your skill needs a specific tool subset or you're publishing for others. Full reference at claude-code-skill-frontmatter-fields.

Body anatomy: instructions, examples, anti-triggers

Everything below the closing --- is the body. Claude reads it as a system-level instruction set once the skill is selected. There's no required structure, but skills that work in production tend to converge on a three-part shape:

Instructions

What to do, step by step, in the order it should happen. Imperative voice. "Open the file," "check that X exists," "if Y, ask the user." Avoid the passive voice and avoid hedging. "Maybe consider checking" is worse than "check."

For complex skills, number the steps. Claude follows numbered lists more reliably than prose paragraphs when the order matters. Save prose for the rationale and the edge cases.

## Instructions

1. Read the current `CHANGELOG.md` from the project root.
2. Locate the `## Unreleased` section. If it doesn't exist, create it at the top of the file under the title.
3. Prepend the user's change as a bullet point under the appropriate subhead (`### Added`, `### Changed`, `### Fixed`, `### Removed`). Default to `### Changed` if unclear.
4. Preserve all existing formatting, blank lines, and trailing whitespace exactly.

Examples

Show one or two concrete invocations end-to-end. Input the user might type, the relevant file state before, the file state after. Examples teach pattern-matching faster than rules do. When Claude sees three rules and one example, the example carries more weight in shaping the output.

Don't overdo it. Two examples is usually enough. Five examples and you've turned the skill into a textbook and the long context starts to hurt rather than help.

Anti-triggers (the section everyone skips)

When NOT to use the skill. We're giving this its own section because it's the single biggest difference between a skill that helps and one that gets in the way. More on this below.

What you don't need

You don't need an ## Installation section in the SKILL.md itself — installation happens by virtue of the file existing in the skills directory. You don't need a license section in the body (use the license: frontmatter field). You don't need a long preamble explaining what the skill does in marketing language — the description already did that, and Claude has already read it. Get to the instructions fast.

Anti-trigger discipline (the big one)

Most beginner skills fail in the same way: they're too eager. They trigger on requests they shouldn't, give confidently wrong advice, and the user disables them within a week. The fix is a section explicitly listing when the skill should stand down.

Call it ## When NOT to use, ## Skip if, or ## Out of scope — the heading wording is less important than the discipline of writing it. Here's a real example from a code-review skill:

## When NOT to use this skill

- The user is asking for a quick summary, not feedback. "What does this file do?" is summarization, not review. Skip.
- The user explicitly says "ship it" or "just merge" — they don't want a review, they want execution.
- The change is a pure typo fix, dependency bump, or auto-generated file (lockfiles, snapshot tests, build artifacts). Review adds no value.
- The user is mid-debugging and asking about behavior. They want diagnosis, not stylistic feedback.
- Files larger than ~2,000 lines. Hand off to a chunked review skill if one is loaded; otherwise tell the user the file is too large and ask which section to focus on.

Why does this work so well? Two reasons.

First, Claude reads the anti-trigger section as part of the description-and-instructions context when deciding whether to invoke. A clear "skip if" list reduces false-positive invocations across the catalog of loaded skills. If you have ten skills loaded and three of them could plausibly match a request, the one with the cleanest negative scope tends to win the right matchups and lose the wrong ones.

Second, even when the skill does invoke, an explicit out-of-scope list keeps it from scope-creeping mid-task. Without it, a code-review skill asked to review a typo fix will helpfully add five paragraphs of unrelated architectural feedback. With it, the skill says "this is a typo fix, no review needed," and stops.

A rule of thumb I use: if I can't write three concrete anti-triggers, I don't understand the skill's scope well enough to ship it yet. The exercise of listing what the skill shouldn't do is how you discover what it should do.

Bad anti-triggers look like "don't use for unrelated tasks." That's a tautology and Claude treats it as noise. Good anti-triggers name specific request shapes, specific file types, specific user intents. The more concrete, the better.

Writing descriptions for auto-discovery

The description is your discovery surface. Here's a side-by-side comparison of the same skill, badly described and well-described:

Bad

description: A skill for code review.

What's wrong: vague, no trigger phrases, no scope, no anti-trigger. Claude will either fire this on every mention of code (false positives) or learn it's a noisy match and start ignoring it (false negatives). Both fail modes ruin the user experience.

Better

description: Run a structured code review when the user asks to "review", "audit", or "critique" specific files or PRs. Focus on correctness, security, and clarity. Skip if the user just wants a summary or explicitly wants to ship without feedback.

Why this works:

The precondition shape

Read your description back to yourself in this form: "When [trigger condition], [action verb] [scope] [optional focus]. Skip if [negative condition]." If you can't fit your description into that shape, it's probably too vague.

Examples that pass the shape test:

One last thing: keep the description under about 500 characters. Long descriptions get truncated in some Claude Code UIs and they crowd the discovery context when many skills are loaded.

Scope: one skill, one job

The biggest mistake after weak anti-triggers is scope sprawl. "I'll make a git-helper skill" sounds reasonable, then six weeks later it covers commit messages, branch naming, merge conflicts, rebasing, PR descriptions, GitHub releases, and tag management. The description has to cover all of those, which means it covers none of them well, and Claude's matching gets unreliable.

The pattern that works: one skill, one verb, one scope. If you find yourself writing "or" in the description more than once, it's probably two skills.

Here's how I'd decompose a hypothetical bloated git-helper:

Skill slugTriggersScope
git-commit-message"write a commit message," "help me commit"Generate a Conventional Commits message from staged diff
git-branch-name"name this branch," "branch for this work"Suggest kebab-case branch name from issue or description
git-rebase-help"rebase," "interactive rebase," "squash commits"Walk through interactive rebase decisions
git-pr-description"write a PR," "draft a pull request"Generate PR title and body from commit log and diff

Four small skills outperform one big one for three reasons.

One: matching is sharper. Claude doesn't have to disambiguate among the six things git-helper does — it picks the skill whose description matches the user's verb.

Two: the body of each skill is shorter and more focused. Claude follows a 40-line instruction list more reliably than a 400-line one, because attention degrades with length and because the relevant section is easier to find.

Three: you can iterate on one without breaking the others. Tweaking the commit-message phrasing doesn't risk regressing the rebase walkthrough.

The trade-off: you have more files to manage. That's it. There's no runtime cost to loading many small skills — Claude Code's session-start scan is fast and the description block is the only thing held in long-context until invocation.

If you're not sure whether to split, write the description first. If the description honestly has to cover multiple distinct verbs ("commit OR branch OR rebase"), split. If you can capture the work in one precondition ("when the user asks to commit"), it's one skill.

The iteration loop (and the restart trap)

Here's where almost everyone loses an hour the first time: Claude Code only loads skills at session start. If you edit your SKILL.md mid-session and re-prompt, your changes will not take effect. The skill you wrote five minutes ago is still the one in context.

The loop that actually works:

  1. Write the SKILL.md. Save the file.
  2. Close your current Claude Code session. Open a fresh one. (How you do this depends on your interface — terminal Claude Code: exit and re-launch. Editor integrations: kill the agent process or open a new conversation.)
  3. Prompt with a phrase that should trigger the skill. Use the natural phrasing a real user would use, not the literal trigger words from your description. If your description says "audit," prompt with "can you review this for me" and see if the skill still fires.
  4. Observe. Did the skill invoke? Did the output follow the instructions? Did Claude scope-creep past the anti-triggers?
  5. Revise the SKILL.md. Save. Restart. Re-prompt. Repeat.

The phrasing test in step 3 is critical. Authors over-fit to their own description. You wrote "when the user asks to review," you prompt "please review this," the skill fires, and you ship. A week later a user types "what would you change about this code" and the skill silently doesn't fire. The trigger phrases in the description need to anticipate paraphrase.

A trick that helps: write the description, then close your eyes for thirty seconds and imagine three different users phrasing the same request. If your description doesn't match at least two of the three, broaden the trigger phrases.

What to look for in observation

Not just "did it work," but:

Three to five iteration cycles is normal for a polished skill. If you're on cycle ten and still chasing edge cases, the scope is probably too broad — go back to the splitting question.

Copy-paste starter template

Here's a full SKILL.md skeleton you can drop into a new directory and fill in. The inline comments mark what to replace and why.

---
# Required. Must match the directory name exactly. Lowercase kebab-case.
name: your-skill-slug

# Required. The trigger. Shape: "When [condition], [verb] [scope]. Skip if [negative]."
# Aim for 200-400 characters. List 2-4 trigger phrases explicitly.
description: When the user asks to [trigger phrase 1], [phrase 2], or [phrase 3], [what the skill does and on what scope]. Skip if [explicit out-of-scope condition].

# Optional. Restrict to specific Claude Code tools. Most skills don't need this.
# allowed-tools: ["Read", "Write", "Bash"]

# Optional. Pin a model. Usually leave unset to use the session default.
# model: claude-sonnet-4-5

# Optional. SPDX identifier. Useful when publishing.
# license: MIT

# Optional. Your name or handle. Useful when publishing.
# author: your-name
---

## Instructions

Describe step-by-step what to do when invoked. Use imperative voice. Number the
steps when order matters.

1. First thing to do.
2. Second thing.
3. If [condition], do [X]; otherwise [Y].
4. Final output: describe the shape of the result.

## Example

**User says:** "a phrase that would trigger this skill"

**Skill does:**

```
Whatever concrete output looks like for this skill.
```

## When NOT to use this skill

- Specific request shape that looks similar but isn't a match.
- File type, scope, or context where this skill adds no value.
- User intent that overlaps but should be handled by [other skill / direct response].
- Edge case where the skill would produce confidently-wrong output.

## Notes

Optional. Any caveats, dependencies, or behavioral details the user should
know about. Keep brief.

Save that as ~/.claude/skills/your-skill-slug/SKILL.md, edit the placeholders, restart Claude Code, and you have a real skill in five minutes. The remaining ten minutes of the fifteen-minute budget go to one or two iteration cycles.

A few tips on filling in the template:

Packaging for distribution

Once you have a skill working locally, there are three ways to share it.

Push to GitHub

The simplest path. Make a public repo, put your skill directory at the root (or under a skills/ subdirectory if you're publishing multiple), and you're done. The repo structure that's become conventional:

your-skill-repo/
├── README.md           # human-facing explanation
├── LICENSE
└── SKILL.md            # the actual skill
# or for multi-skill repos:
your-skill-repo/
├── README.md
├── LICENSE
└── skills/
    ├── skill-one/
    │   └── SKILL.md
    └── skill-two/
        └── SKILL.md

Users install it by cloning into ~/.claude/skills/ or copying the SKILL.md into a directory there. The full step-by-step is at installing Claude Code skills.

If you want your skill to be findable, give the repo a README that explains what it does in human terms (the SKILL.md description is for Claude; the README is for humans browsing GitHub), tag it with claude-code-skill or skill-md as topics, and the various skill catalogs will eventually crawl it. You can also submit directly via the form on the ClaudSkills homepage — that gets it into the index within a daily cycle.

Ship as a plugin

For more sophisticated distributions — multi-skill bundles, ones with shared configuration, ones that need a structured install flow — the plugin format wraps a skills directory with a .claude-plugin/plugin.json manifest. It's overkill for a single skill but it's the right shape if you're publishing a coherent set: a code-review plugin that bundles four review-related skills, for example.

The plugin format is documented separately and is mostly relevant if you're building a paid or distribution-grade product. For most authors, the bare GitHub repo approach is fine for the lifetime of the skill.

Browse what others have shipped

Before you publish, look at how working skills in your problem space are written. The curated list is a good entry point, and the various category pages — engineering, devops, content — each have a few exemplary skills near the top whose SKILL.md files are worth reading start-to-finish. Skills with strong anti-trigger sections, tight scopes, and concrete examples in the body are the ones that survive contact with real users. Once you've read three or four of them, the patterns in this guide will click into place faster than any amount of prose explanation.

One last thing

After you ship, use your own skill for a week before declaring it done. Real usage surfaces edge cases that no amount of pre-publication iteration finds. The version of any skill that survives a month of daily use looks meaningfully different from the version that left your machine on day one. That's not a failure of authorship — it's the iteration loop doing its job at a longer timescale.

Frequently asked questions

Do I need to restart Claude Code every time I edit a SKILL.md?
Yes. Claude Code loads the skill index at session start and holds it for the lifetime of the session. Mid-session edits to a SKILL.md file have no effect until you close and reopen the session. This is the single most common source of "I edited the skill but nothing changed" confusion.
What's the minimum a SKILL.md file needs to work?
A YAML frontmatter block with name and description, both required, plus at least one line of body content. About five lines total. The directory containing SKILL.md must match the name field exactly and live under ~/.claude/skills/ (user-scope) or .claude/skills/ (project-scope).
Why isn't my skill being invoked even though it loaded?
Usually one of three things: the description is too vague to match the user's phrasing, a competing skill has a stronger description match, or the description's trigger phrases don't anticipate paraphrase. Try prompting with three different ways a user might phrase the same request — if only one fires the skill, broaden the trigger phrases.
How long should my description be?
Roughly 200-500 characters. Long enough to name 2-4 trigger phrases, scope, and an explicit skip clause. Short enough that it doesn't get truncated in UIs that show skill metadata. If you're under 100 characters, you almost certainly haven't named enough trigger phrases.
Can a single skill do multiple things?
Technically yes, practically you'll regret it. Scope-bloated skills have descriptions that have to cover several verbs, which weakens matching across all of them. The rule of thumb: if your description uses "or" more than once between distinct actions, split it into multiple skills.
Do I need an anti-trigger section if my scope is obvious?
Yes. The obvious scope is obvious to you, the author. To Claude reading among many loaded skills, the negative scope is part of how it disambiguates close matches. Skills with explicit "when NOT to use" sections invoke more accurately and stay scoped during execution. It's the highest-leverage 60 seconds of authoring effort.
Where do project-specific skills go versus global ones?
Global skills available across all your projects go in ~/.claude/skills/. Project-specific skills go in .claude/skills/ inside the repo and load only when Claude Code runs in that project. The project-scope variant is great for skills that encode codebase conventions you don't want bleeding into other work.

Found a bug or want a topic covered? Email [email protected] or open an issue via GitHub.