The moment to write one
The cost of a skill is about five minutes. The cost of not writing it is retyping a paragraph forever, slightly differently each time, and getting slightly different results because of it.
01 · Where they live
A skill is a folder with a SKILL.md inside. The folder name becomes the command name.
| Location | Scope | Invoked as |
|---|---|---|
~/.claude/skills/<name>/SKILL.md | you, every project | /<name> |
.claude/skills/<name>/SKILL.md | this project, committed to git | /<name> |
.claude/commands/<name>.md | legacy command file, still works | /<name> |
Legacy command files namespace by subdirectory: .claude/commands/frontend/component.md becomes /frontend:component.
02 · What a SKILL.md looks like
YAML frontmatter, then plain markdown instructions. That is the whole format.
---
name: review
description: Review the current diff for bugs before I open a PR
argument-hint: [branch]
---
## Review the diff against $ARGUMENTS
1. Read the full diff, not just the changed lines
2. Flag anything that would fail CI
3. Check the tests actually cover the new branch of logic
4. Report findings worst-first, and stop
The fields worth knowing:
| Field | What it does |
|---|---|
description | when Claude should reach for this by itself — the one field you should always write |
argument-hint | what shows in autocomplete after you type the command |
allowed-tools | tools pre-approved for this turn, so it does not stop to ask |
disable-model-invocation | only you can run it; Claude will not trigger it on its own |
user-invocable | set false to hide it from the / menu and let only Claude use it |
03 · Arguments
Everything typed after the command lands in $ARGUMENTS:
Summarise these changes: $ARGUMENTS
/summarise add search to the header → Claude sees the whole sentence.
For more than one, use positions:
---
arguments: [component, fromLang, toLang]
---
Migrate the $component component from $fromLang to $toLang.
Preserve behaviour and tests.
/migrate Button JavaScript TypeScript.
04 · The part that makes skills actually useful
A skill does not have to be static text. It can run commands and paste the real output into the prompt before Claude ever reads it.
## Current state
!`git status --short`
!`git diff --stat`
## Instructions
Review only the files listed above.
And it can pull whole files in with @:
## Conventions to follow
@./.eslintrc.json
## Code under review
@./src/main.js
This is the difference between a snippet and a tool.
A skill with !`git diff` in it is never stale. It describes how to look, and the looking happens fresh every time you run it.
05 · Pre-approving the tools it needs
A skill that stops four times to ask permission is not saving you anything. allowed-tools grants exactly what this command needs, for this turn only:
---
name: commit
description: Stage and commit the current changes
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
---
Stage the changes, write a message describing why rather than what,
and commit. Do not push.
Note disable-model-invocation: true. Anything that writes, deploys or publishes should be something you trigger deliberately, not something the model can decide to run.
06 · A full one, end to end
---
name: fix-issue
description: Fix a GitHub issue by number. Use when asked to fix an issue.
argument-hint: [issue-number]
arguments: [issue]
allowed-tools: Bash(gh issue view *)
disable-model-invocation: true
---
## Fix issue #$issue
### The issue
!`gh issue view $issue --json title,body,comments`
### Steps
1. Understand what is actually being reported above
2. Find the relevant files yourself
3. Implement the fix in the smallest change that works
4. Run `npm test`
5. Commit as `fix(#$issue): <what changed>`
Run /fix-issue 123 and Claude starts with the real issue text already in front of it, rather than asking you to paste it.
Rules of thumb
- Write the
descriptionfor Claude, not for yourself — it is what decides whether the skill gets picked up automatically. - Keep
SKILL.mdshort. It loads when invoked, so a long one costs context every time. - Ground it in live data with
!instead of describing the state in prose. - Anything destructive gets
disable-model-invocation: true.
Verified against the official Claude Code documentation, September 2026. Claude Code moves quickly, so check the official documentation before relying on a detail here.