Privacy choices

Optional Google Analytics and advertising are off until you choose. Read our privacy details.

ClaudeLevel / intermediate9 min field guide

Claude Code skills: write one that actually fires

SKILL.md basics plus the description rewrite that took one of my skills from 50% to 100% trigger rate

By
Claude Code skills guide card showing SKILL.md structure and trigger testing
FIELD GUIDE · CLAUDE · SEP 28, 2026

Every Claude Code skill is a single file, SKILL.md, sitting in a folder whose name becomes a command. The official documentation puts it directly: "Skills extend what Claude can do. Create a SKILL.md file with instructions, and Claude adds it to its toolkit. Claude uses skills when relevant, or you can invoke one directly with /skill-name."

Whether that second path happens without you typing anything comes down to one field: the description. I found out how much it matters the expensive way. I write my own skills for the Claude Code setup I run daily, and one of them, blindspot (a "what am I missing" pass before I start a build), I shipped and measured the next day against ten prompts from its own source material. It fired on five of ten: nothing in the SKILL.md body was wrong, the description was.

This piece covers what a skill is, where the file goes, how to write and test one, and the fix that took my recall from 50 percent to 100 percent. It pairs with the Claude Code playbook for the rest of your setup.

What a skill is

Every skill is one file, SKILL.md, split into two parts: YAML frontmatter between --- markers that tells Claude when to use it, and markdown instructions below that Claude reads once it runs the skill.

The command you type comes from the directory name, unless the frontmatter sets its own name field. A folder at .claude/skills/deploy-staging/SKILL.md becomes /deploy-staging; add name: deploy and it becomes /deploy.

The description field carries the triggering decision: Claude Code loads every skill's name and description into context, but the full file loads only once invoked, by you typing its name or by Claude matching your message to it.

Custom commands and skills are the same system now: a file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy. Older command files still work, but a skill folder can also hold supporting files (reference docs, bundled scripts) next to SKILL.md, which a plain command file cannot.

Where skills live

Where you save a skill decides which sessions load it. Four locations cover a normal setup:

Location Path Loads in
Enterprise .claude/skills/<skill-name>/SKILL.md in the managed settings directory All users where the organization deploys it
Personal ~/.claude/skills/<skill-name>/SKILL.md All your projects on this machine, but not Cowork or cloud sessions
Project .claude/skills/<skill-name>/SKILL.md Sessions in this repository, once committed
Plugin <plugin>/skills/<skill-name>/SKILL.md Wherever the plugin is enabled, as /plugin-name:skill-name

My own skills, including blindspot, sit in the Personal row, on the Ubuntu 24.04 desktop machine I run Claude Code from. It is a handful of skills, not a library, which already makes one bad description expensive: every skill's description takes a share of the listing Claude reads when it decides what to load.

Write your first skill

My machine runs Claude Code 2.1.283 as I write this (claude --version prints 2.1.283 (Claude Code)). The documentation gates skill behavior by version often enough that you should check yours before assuming every detail here applies as written.

The documentation's own example summarizes uncommitted git changes, a clean first skill since it mixes a real trigger with a real command.

  1. Create the skill directory. This is the Personal location from the table above:
mkdir -p ~/.claude/skills/summarize-changes
  1. Write SKILL.md. Save this to ~/.claude/skills/summarize-changes/SKILL.md:
---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullet points, then list any risks you notice such as missing error handling, hardcoded values, or tests that need updating. If the diff is empty, say there are no uncommitted changes.

The ! line is dynamic context injection: Claude Code runs that shell command and replaces the line with its output before Claude sees the file, so the instructions arrive with your diff already inside them.

  1. Test it two ways. Open a git project with an uncommitted edit, start Claude Code, and ask a plain question that matches the description:
What did I change?

Or invoke the skill directly:

/summarize-changes

Either way, Claude should answer with a short summary of the edit and its risks. If neither works, Troubleshooting below covers the two most common reasons.

The frontmatter fields that matter

These fields matter most beyond the example above, one line each:

  • name: sets the command you type. Defaults to the directory name if you leave it out.
  • description: what the skill does and when to use it. Claude uses this to decide whether to load it on its own; omit it and Claude falls back to the markdown body's first line instead.
  • disable-model-invocation: set to true and only you can run the skill, with /name. Claude will not trigger it automatically. The documentation recommends this for anything with side effects, like a deploy or a commit.
  • allowed-tools: tools Claude can use without an approval prompt for the turn that invokes the skill. The grant clears the moment you send your next message.

Why my skill fired only half the time

Two of blindspot's five misses, described above, contained keywords my description explicitly listed, which ruled out the easy explanation: the right words were present, but how they were arranged was not.

The real issue was that my description read like a policy for a human reviewer, listing conditions the way a rulebook would. Claude's router needs the concrete situations spelled out in the words a person would actually type, an explicit push toward using the skill, and an explicit list of when not to fire.

Here is a generic illustration, not my real description. A policy-style line reads like: "Use this skill for planning and scoping decisions when the user is uncertain." A router-style rewrite reads like: "Always use this skill, even when the user never says a specific keyword, whenever they say they do not know something, ask what they are missing, or drop a one-line request onto a broad new surface. Do not fire once they have already approved a scoped task." The second gives the router situations to match; the first gives it a policy to interpret, which is where my misses happened.

How I test a skill now

After the rewrite, I ran the same skill against twenty test prompts, ten that should trigger it and ten that should not: 100 percent recall, precision, and accuracy. The skill's body never changed; the description was the entire fix, and it is the only number I trust for this one skill.

One should-not-fire prompt, asking what had been decided the previous week, correctly went to a different skill of mine that handles memory search instead of blindspot. A pushier description could just as easily steal a neighboring skill's traffic, and here it did not.

My test file is a flat JSON list of query and expected-outcome pairs:

[
  {"query": "what am I missing before I start this build", "should_trigger": true},
  {"query": "what did we decide about the database last week", "should_trigger": false}
]

I re-run that file after every description edit, since the edit is cheap and a silently broken description is easy to miss.

Anthropic ships an official version of the same idea. The skill-creator plugin, installed with /plugin install skill-creator@claude-plugins-official, includes a description-tuning mode: it generates should-trigger and should-not-fire prompts, measures the hit rate, and proposes edits when a skill fires wrong. Anthropic's own skill-creator file names the failure mode it is fixing, that Claude tends to under-trigger skills, and its fix is to "make the skill descriptions a little bit "pushy"."

When a skill is the wrong tool

  • A rule that has to hold in every session, not only when a description matches: that belongs in CLAUDE.md. The documentation describes CLAUDE.md files as ones that "give Claude persistent instructions for a project, your personal workflow, or your entire organization. You write these files in plain text; Claude reads them at the start of every session."
  • An action that has to run at a fixed point with no chance of being skipped: that is a hook. The documentation defines hooks as "user-defined shell commands, HTTP endpoints, MCP tool calls, LLM prompts, or subagents that execute automatically at specific points in Claude Code's lifecycle."
  • A task that needs its own context, separate from the conversation already running: that is a subagent. The documentation puts it this way: "Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions." A skill, by contrast, loads into the conversation you are already in.

Troubleshooting

  • Skill not triggering: check that the description uses words a user would actually type, and invoke it directly with /skill-name if it is user-invocable. A malformed frontmatter block is a common silent cause: the skill loads with empty metadata, so the command still works while Claude has nothing to match against; --debug shows the parse error.
  • Skill triggers too often: narrow the description, or add disable-model-invocation: true so only you can run it.
  • Skill descriptions get cut short: past a budget tied to your model's context window, Claude Code drops descriptions, starting with the skills you invoke least. Put the key use case first: description and when_to_use text together are capped at 1,536 characters regardless.
  • Personal skills disappear: check ~/.claude/skills/.trash/ before assuming a skill is gone for good. Before version 2.1.280, a manifest.json file there could move listed skill folders into a timestamped trash folder, cleared only after a default 30-day retention sweep.

Sources

Filed under