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

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.
- Create the skill directory. This is the Personal location from the table above:
mkdir -p ~/.claude/skills/summarize-changes
- 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.
- 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-nameif 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;--debugshows the parse error. - Skill triggers too often: narrow the description, or add
disable-model-invocation: trueso 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, amanifest.jsonfile there could move listed skill folders into a timestamped trash folder, cleared only after a default 30-day retention sweep.
Related
- subagents and the fork flag: how forked subagents share the parent's context, and when parallel writers need separate worktrees instead.
- Claude Code pricing: what Pro, Max and API access to Claude Code cost today.
- the Linux install walkthrough: the base setup, if Claude Code is not installed yet.
- building a custom MCP server in Python: when the capability belongs in a tool call rather than a markdown file.
- a CLI coding agent fleet in tmux: for sessions that each load their own project skills in parallel.
Sources
- Extend Claude with skills, accessed 28 Sep 2026
- Skill Creator, accessed 28 Sep 2026
- How Claude remembers your project, accessed 28 Sep 2026
- Hooks reference, accessed 28 Sep 2026
- Create custom subagents, accessed 28 Sep 2026
Filed under
More Claude from the desk.

Claude Code pricing: Pro vs Max vs API
Claude Code is not free: it comes bundled with a Pro or Max subscription, or you pay per token through the API. This piece lays out what each rail costs today so you can pick the one that fits how much you code.

Cline vs Aider in 2026: Coding Agent Pick
Cline vs Aider in 2026, judged by someone who lives in coding agents daily. The interface split is real, but the thing that decides whether you ship is per-task routing, not the tool logo. Here is the working model.

Claude Code to Prod With No Engineering Title
A non-traditional operator running real production sites on Claude Code, Coolify, PocketBase on Oracle ARM, and Cloudflare. The real workflow is Opus as orchestrator plus subagents executing, governed by hard gates: verify before claiming done, snapshot before destructive ops, and a flat refusal of --no-verify and --force. Here is what works, and the failure modes a non-engineer hits shipping to prod in India.