How to Write a Claude Code Skill From Scratch (With a Real Example)
Most developers who use Claude Code every day have never written a single skill. Which is strange, because the format is one of the simplest things in modern developer tooling — five lines of frontmatter, some markdown, and you've got a new slash command available in every session.
This is the full walkthrough. By the end, you'll have a working /review skill, you'll know when to make a skill vs. just type the prompt, and you'll know the pitfalls that cause skills to silently not fire.
If you haven't seen the wider picture of how Skills fit alongside CLAUDE.md, hooks, memory, and settings, start here first. This post zooms in on the authoring part.
What a skill actually is
A skill is a directory under ~/.claude/skills/ (user-wide) or .claude/skills/ (project-scoped) with a single required file:
~/.claude/skills/
└── review/
└── SKILL.md
That's it. The directory name becomes the slash command — typing /review in a Claude Code session invokes the skill at ~/.claude/skills/review/SKILL.md. The skill can ship with extra files alongside SKILL.md (templates, scripts, examples), but they're optional.
Skills vs slash commands. You may also see references to ~/.claude/commands/<name>.md — that's the older single-file slash command format. Both still work; both invoke the same way. Skills are the newer, richer format. For new work, default to Skills.
The minimum viable skill
Here's a complete, working skill — five lines:
---
name: hello
description: Print a greeting and the current time.
---
Greet the user by name and report the current local time.
Save that as ~/.claude/skills/hello/SKILL.md, start a new Claude Code session, type /hello, and Claude will do exactly what it says.
Most "what is a skill" explanations make this feel more complicated than it is. It's a prompt with a name and a description.
The SKILL.md format
The full anatomy:
---
name: <slug — must match the directory name>
description: <one-line summary of when to use this skill>
allowed-tools: <optional comma list — restricts which tools the skill may use>
---
<your instructions to Claude, in markdown>
The fields:
name — must match the directory name exactly. Lowercase, kebab-case.
description — by far the most important field. Claude uses it to decide whether to apply the skill when reasoning about how to handle a request. A good description names the trigger condition ("when the user wants to review a PR…") and the outcome ("…check for security issues and produce a punch list"). A vague description means Claude never picks the skill up.
allowed-tools — optional. Restrict the skill to a subset of Claude's tools (e.g. Read, Bash, Grep). Useful for skills that should never touch the filesystem in destructive ways.
After the frontmatter, the body is whatever instructions you want Claude to follow when the skill runs. Plain markdown. Structure it however helps you reason — numbered steps, headings, decision trees, all fine.
Step 1: Pick a workflow worth automating
Not every prompt deserves a skill. Good candidates:
- You've prompted variations of this thing more than three times. Same kind of work, slightly different inputs.
- The workflow has multiple steps that need to chain. Read this file, then run that command, then check the result.
- The output needs to be consistent. Reviews, summaries, PR descriptions, test plans.
- You'd hand this to a new teammate as a "here's how we do X" doc.
Anti-patterns:
- One-liner prompts. "Refactor this function" is faster to type than to invoke
/refactor. Save the slash for things that have structure.
- One-off explorations. Skills are for repeatable work, not for thinking out loud.
- Things that need too much context. If the skill body has to re-explain your entire codebase, what you really need is
CLAUDE.md, not a skill.
The single best signal: you've written this prompt before, and you'll write it again next week.
Step 2: Write the frontmatter
Start with the description. Spend more time on this than the body. The description is what Claude reads when deciding to use the skill — bad description, dead skill.
Bad description:
description: Review code.
(Too vague. Claude won't know when to use it.)
Better:
description: Review a pull request for security issues, missing tests, and style violations. Use when the user mentions a PR by number or asks for a code review.
Notice: it states the trigger (PR review request) and the outcome (security + tests + style). Both matter.
If your skill should restrict tool use, add allowed-tools:
allowed-tools: Read, Grep, Bash(git diff), Bash(git log)
That whitelist lets the skill read files and inspect git, but can't Edit, Write, or run arbitrary Bash.
Step 3: Write the instructions
This is where most people overthink it. The body of SKILL.md is just a prompt — written in the second person, as instructions to Claude.
A pattern that works well:
## When to use this skill
<conditions that should trigger it>
## What to do
1. <first concrete action>
2. <second action>
3. <...>
## Output format
<what the final response should look like>
## Common pitfalls
<known traps Claude should avoid>
Some things that genuinely help:
- Number the steps. Claude follows lists more reliably than prose paragraphs.
- Be specific about file paths. "Read
app/api/src/router.ts" beats "look at the router."
- State the format of the final answer. "Reply with a markdown table" or "produce a JSON object with these fields."
- Include error handling. "If the PR doesn't exist, tell the user and stop — don't make up review feedback."
Things that don't help:
- Long preambles ("You are an expert software engineer with 20 years of experience…"). Claude doesn't need this. Get to the steps.
- Trying to enumerate every edge case. Cover the main path; trust the model on the rest.
Step 4: Test it
After saving the file:
- Start a new Claude Code session. Existing sessions don't pick up new skills mid-run.
- Type
/ to see the autocomplete list. Your skill should appear by name. If it doesn't, the most likely cause is a mismatched name field vs. directory name.
- Invoke it on a real case. Pass arguments inline:
/review #112.
- Watch what Claude does. If it skips steps, ignores the format, or seems confused, that's a description or instruction problem — iterate on
SKILL.md.
The fastest feedback loop is to keep SKILL.md open in your editor and a fresh Claude Code session in another terminal. Edit, save, new session, test, repeat.
A real example: a /review skill
Here's a complete, useful skill you can drop in today. Save as ~/.claude/skills/review/SKILL.md:
---
name: review
description: Review a pull request for security issues, missing tests, and style violations. Use when the user mentions a PR number or asks for a code review.
allowed-tools: Read, Grep, Glob, Bash(gh pr view:*), Bash(gh pr diff:*), Bash(git log:*)
---
## When to use this skill
The user has asked for a code review or referenced a PR by number
(e.g. "review #112", "look at the auth PR").
## What to do
1. Fetch the PR metadata: `gh pr view <N> --json title,body,files`
2. Fetch the diff: `gh pr diff <N>`
3. Read each modified file in full (not just the diff context).
4. Check for, in this order:
- **Security**: hardcoded secrets, SQL injection, unsafe eval,
missing auth checks on sensitive endpoints.
- **Tests**: any new public function or endpoint without a test?
- **Style**: violations of the conventions in CLAUDE.md or repo docs.
- **Correctness**: obvious off-by-ones, race conditions, error-handling
gaps, unhandled promise rejections.
## Output format
Reply with three sections:
### Must fix
- <bullet list — items that block merge>
### Should consider
- <bullet list — items worth discussing but not blocking>
### Looks good
- <one or two sentences calling out what's well done>
## Common pitfalls
- Don't paraphrase the diff back to the user. They've read it.
- If a file's purpose is unclear from its name, read the imports of the
files that use it before commenting.
- If there are zero issues, say so. Don't manufacture concerns.
In any new session: /review 115. Claude will fetch the PR, read the files, and produce the three-section output.
You can adapt the same pattern for /ship, /explain, /test-plan, /changelog-entry, or whatever else you do regularly.
Project skills vs user skills
You have two places to put skills:
~/.claude/skills/<name>/ — user-wide. Available in every session, every project. For workflows that aren't tied to any specific repo: /review, /changelog-entry, /explain-this-error.
<repo>/.claude/skills/<name>/ — project-scoped. Only available inside that repo. Commit them to share with teammates. For workflows tied to the codebase: /ship (with your repo's branch conventions), /run-migration, /deploy-staging.
Project-scoped skills with the same name as a user skill take precedence inside that repo. Use this to override your defaults for one project — for example, a per-repo /ship that uses Conventional Commits + your repo's release-please flow.
Bonus: supporting files
The skill directory can hold more than just SKILL.md. Common patterns:
~/.claude/skills/changelog-entry/
├── SKILL.md
├── template.md ← format Claude renders into
└── examples/
└── good-entry.md ← reference Claude can read for tone
Reference them from SKILL.md:
Use the format at `~/.claude/skills/changelog-entry/template.md`.
Match the tone of the example entries in the `examples/` directory.
Claude can Read them like any other file. Keep them small — they consume context budget every time the skill runs.
The honest part: writing skills is the easy bit
Now the inconvenient truth. The actual hard part isn't writing the SKILL.md — that's 10 minutes. The hard part is noticing which workflows deserve a skill in the first place.
You write maybe 50 prompts a day. A handful of them are versions of prompts you've written before. You don't remember which ones. You don't keep score. By Friday, the prompts have evaporated and the patterns are invisible.
Three months later, you wonder why your skill library has only two skills in it when you clearly do dozens of repeated workflows. The answer is: you've never seen the data. Your transcripts have it; nobody's reading them.
This is the gap Prompt Conduit closes. It reads your ~/.claude/projects/ directory (your Claude Code transcripts), finds sequences of prompts and tool calls that repeat across sessions, and writes them as Skills in ~/.claude/skills/. Local-only mode requires no account:
brew install promptconduit/tap/promptconduit
promptconduit skills generate --local --dry-run # preview first
promptconduit skills generate --local # write them
You still own the skills. They're plain SKILL.md files Claude Code reads natively — you can edit, version-control, or delete them like any other. We just bridge the gap between "I do this workflow" and "I have a slash command for it."
If you'd rather keep authoring by hand, that's fine too — the rest of this post still applies. The format is simple enough that there's no excuse for not having at least a few skills by next Friday.
FAQ
What's the difference between a skill and a slash command?
Slash commands are the older format (~/.claude/commands/<name>.md — single file). Skills are the newer format (~/.claude/skills/<name>/SKILL.md — directory). Both invoke the same way (/<name>). Skills can carry supporting files; slash commands can't. For new work, write Skills.
Can a skill call another skill?
Indirectly — you can write Use the /review skill on this PR in the body, and Claude will follow that. There's no first-class "import" mechanism; it's just text.
Can skills restrict which tools Claude uses?
Yes — the allowed-tools frontmatter field. List the tools (and optionally specific bash command patterns) the skill is allowed to use. Anything outside the list requires explicit user permission to invoke.
What happens if my skill is wrong and Claude ignores it?
The most common cause is a vague description — Claude doesn't recognize that this skill is the right tool for the user's request. Rewrite the description with a clear trigger condition. Second most common cause: the skill body conflicts with CLAUDE.md (e.g. CLAUDE.md says "always use pnpm" but the skill says "run npm test"). Reconcile them.
Where can I find example skills?
PromptConduit's local skill generation writes useful examples to ~/.claude/skills/ based on your own usage. Several repos on GitHub also publish skill collections — search for awesome-claude-code or claude-code-skills. Copy what works for your workflow; ignore the rest.
Do skills work in subagents (Task tool)?
Yes, when the subagent inherits your skill directory. Sub-agents launched via the Task tool see the same ~/.claude/skills/ and project .claude/skills/.
Does the order of skills matter?
Not really — Claude reads all available skills' descriptions and picks the most relevant for the task. Naming conventions don't affect priority.
Further reading