The one skill that teaches the rest: how to write a SKILL.md and how to make Claude use it.
New to skills? Watch, then try it
A few minutes, no code. See how a skill works, then build your first one step by step.
Scene 1 of 6 · Your helper forgets
Why skills exist
Same ask, different pancakes
Every morning you ask your AI helper for pancakes. Every morning they come out a bit different.
That is because you explain it a little differently each time, and the helper does not remember.
"make my pancakes please"
Monday
Tuesday
Wednesday
What a skill is
A skill is a recipe card for your AI
You write it once. The helper follows it every time.
On a computer, this card is a small text file called SKILL.md. No coding needed.
Namepancake-maker
the label
When to use itWhen someone asks for pancakes, flapjacks or a hot breakfast
the magic note
Steps1. Mix flour, milk, eggs 2. Pour small circles 3. Flip when bubbles pop
the recipe
What it looks likeThree fluffy pancakes, syrup on the side
what "done" means
How the helper finds it
The helper reads only the labels first
It keeps a box of cards. It reads just the name and the "when" note on each one.
When a note matches what you asked, it pulls that card out and reads the whole recipe.
"can I get flapjacks?"
email-helperWhen someone wants to write or reply to an email
pancake-makerWhen someone asks for pancakes, flapjacks or a hot breakfast
homework-checkerWhen someone wants homework checked for mistakes
The most important line
A vague note never gets picked
Write down the real words people say when they want this. The more ways you list, the more often it gets picked.
Checking it works
Ask for it the way real people do
Try a few different ways of asking. If the helper misses one, add those words to the "when" note and try again.
Cards get better a little at a time. That is normal.
When someone asks for pancakes, flapjacks or a hot breakfast
Now you
Make your own card
Pick a topic to start from an example, or type your own. Every box checks itself and shows you how to fix it.
Signed in? Tell the AI what you want changed and it refines the whole card for you.
What it does
You explain the same task to an AI assistant over and over, and every time you get something slightly different back. The instructions live in your head, so they arrive in a slightly different shape each time, and the result drifts with them.
A skill is a single file that holds those instructions permanently. This one teaches you to write it: what the two lines at the top do, how to describe the job so the assistant recognises the request without being told, what to put in the body, and where to save the file so it loads on its own. It also covers the part people usually miss, which is testing whether the skill actually activates when you ask normally rather than only when you name it.
You finish with a working skill file, a short set of test requests that prove it fires on the right ones and stays quiet on the near-misses, and a review checklist you can reuse for the next one.
You end up with a working skill file, test requests that prove it triggers, and a checklist for the next one.
When to reach for it
Use it the first time you notice you are re-explaining a task, and any time an existing skill is being ignored or gives inconsistent results. It is also the right place to start if you want to understand how every other skill on this page works before copying one.
Skip it for a one-off task. Writing a skill only pays off for work that repeats, and a single request is faster to just ask for.
---
name: write-and-call-skills
description: Teaches how to write a skill (a SKILL.md file that gives an AI assistant one repeatable job) and how to get it to actually run - frontmatter rules, description writing that triggers reliably, where to save the file, and how to test it. Use when creating a new skill, improving one, or when a skill exists but never activates.
license: Apache-2.0
metadata:
source: anthropics/skills
---
# Write and Call Skills
You help someone turn a task they repeat into a skill: one file that teaches an assistant how to do that job their way, every time. You also make sure it gets used, which is a separate problem from writing it well.
A skill is not code. If the person can describe the job as a checklist, they can have a skill.
## How a skill loads
Three levels, and knowing them is what keeps a skill small:
1. **Name and description** are always in view. This is the only part the assistant sees before deciding to open the skill. Roughly 100 words.
2. **The SKILL.md body** loads when the skill triggers. Keep it under 500 lines; under 250 is better.
3. **Bundled files** (`references/`, `scripts/`, `assets/`) load only when the body points at them, and a script can run without ever being read.
So the body carries the workflow, and anything long, rarely needed, or purely illustrative moves into a bundled file that the body names.
## Workflow
1. **Find the real job.** Ask what task they repeat, what a good result looks like, and what has gone wrong before. If the current conversation already contains the workflow ("turn this into a skill"), pull the steps, corrections, and formats out of the history first and confirm them.
2. **Narrow it to one job.** "Summarize meetings" is a skill. "Handle my communication" is a category. Split anything that has two unrelated outputs.
3. **Draft the frontmatter.** Name and description, in the exact form below. Do this before the body, because the description forces you to state what the skill is for.
4. **Write the body.** Role line, workflow steps, an example, rules for thin or ambiguous input. Explain why each instruction matters instead of stacking commands in capitals; the reasoning is what makes the assistant handle a case you did not anticipate.
5. **Show the output shape.** The exact sections, order, and length limits. A skill without an output shape drifts run to run.
6. **Push depth into bundled files.** Long examples, lookup tables, and per-framework variants go to `references/`. Repeated deterministic work goes to `scripts/`.
7. **Validate the file.** Run `scripts/validate_skill.py` on the folder to catch the mechanical errors before wasting a test run on them.
8. **Test triggering and doing, separately.** A skill can be well written and never fire, or fire on the wrong request. See "Test it properly" below.
9. **Improve from real failures.** Each time the output is wrong in a specific way, add the one line that fixes that case. Skills get good by accretion, not by being written well once.
```text
Skill review checklist
[ ] One job, stated in a sentence
[ ] Name is kebab-case and matches the folder name
[ ] Description says what it does AND when to use it, in third person
[ ] Body has a workflow, an example, and rules for thin input
[ ] Output shape is explicit
[ ] Every bundled file is mentioned in the body, with when to read or run it
[ ] Nothing time-sensitive (versions, prices, dates) in the body
[ ] Tested on a real task in a fresh session, with none of the author's context
```
## The frontmatter
```markdown
---
name: meeting-notes-to-actions
description: Turns a meeting transcript or rough notes into a list of decisions and owned action items with dates. Use when someone shares notes, a transcript, or a recording summary and wants to know who agreed to do what by when.
---
```
Two fields carry all the weight.
**name** is a short id, lowercase with hyphens, matching the folder it lives in. No version numbers.
**description** is the trigger. It is the only text the assistant reads when deciding whether to open your skill, so it must contain both halves:
- *what it does*, in third person ("Turns a transcript into..."), not first person and not a title;
- *when to use it*, in the words a person would actually use, including the file types, tools, or phrases that signal this task.
Assistants under-trigger far more often than they over-trigger, so lean explicit: name the situations rather than hinting at them. Compare:
- Weak: `description: Helps with dashboards.`
- Strong: `description: Builds a fast single-page dashboard from a data file or query result, with the chart types chosen to match the data. Use whenever someone mentions a dashboard, a metrics view, charts of internal data, or wants to "see the numbers" from a spreadsheet or export.`
The second one fires. The first one sits there.
## The starter file
A new skill begins as six lines. Fill the frontmatter, then write the body under the heading.
```markdown
---
name: skill-name-here
description: What this skill does, and when it should be used.
---
# Skill Name Here
```
## Where the file goes
- **claude.ai** - paste the skill into a Project's custom instructions, or keep it in a document and paste it at the start of a chat.
- **One repository** - `.claude/skills/<name>/SKILL.md`, committed with the project so the team gets it too.
- **Every project on your machine** - `~/.claude/skills/<name>/SKILL.md`.
Bundled files sit beside SKILL.md in that same folder: `references/examples.md`, `scripts/check.py`, and so on. Reference them from the body with relative forward-slash paths, one level deep.
## How a skill gets called
Most of the time nobody calls it. The person asks for the task in their own words, the description matches, and the skill loads. That is the design, and it is why the description matters more than the body.
To be explicit:
- Name it: "Use the meeting-notes-to-actions skill on this transcript."
- In Claude Code, type `/` and pick the skill from the list.
- In a Project, a skill in the instructions is always active for that Project.
If a skill never activates on its own, the fix is almost always the description, not the body.
## Test it properly
**Does it fire?** Write about ten realistic requests: six or seven that should trigger the skill, three or four near-misses that should not. Near-misses are the useful ones - requests that share vocabulary but need something else. Run them in fresh sessions and note which loaded the skill. Anything that should have fired and did not is a missing situation in the description.
Keep the test requests concrete and messy, the way people actually type: file names, a bit of backstory, lowercase, abbreviations. A one-line request like "read this file" will not trigger any skill, because the assistant can just do it; those make poor test cases.
**Does it work?** Give the skill a real task in a fresh session, with none of the context that is in the author's head. Every place the result surprises you is information that belonged in the skill.
## Example
**Request.** "I write the same kind of release note every sprint and they come out inconsistent. Make this a skill."
**What you produce.** First, the frontmatter, checked with them:
```markdown
---
name: release-notes-writer
description: Writes customer-facing release notes from a list of merged pull requests or a changelog, grouped into new features, improvements, and fixes, in plain language with no internal jargon. Use when someone shares a changelog, a list of merged PRs, or asks for release notes or "what shipped this sprint".
---
```
Then a body with: a one-line role statement, the workflow (group changes, drop internal-only work, rewrite each line as a user-visible outcome, order by impact), the exact output shape (three headed sections, one line each, no version numbers in the headings), and two rules learned from their past failures ("never invent a benefit the change does not deliver", "keep the team's own product names, do not rename features").
Then the trigger test: "write release notes for this changelog" fires it, and so does "what shipped this sprint"; "write a blog post about our new feature" correctly does not.
## Rules
- One job per skill. Two jobs means two files.
- Write the body to the assistant, in imperative voice, but keep it readable for the human who will edit it later.
- Explain why a rule exists. A rule with a reason survives cases you did not think of; a bare command does not.
- Keep time-sensitive facts out. Model names, prices, and version numbers age badly inside a file nobody re-reads.
- Never write a skill that hides what it does. If describing the skill honestly would surprise the person running it, it is the wrong skill.
- If the body passes about 400 lines, that is a structure problem: move the depth into `references/` and leave a pointer.
## Edge cases
- **The person is not sure what the skill should do.** Do not draft yet. Ask for one real example of the task, done well, and generalize from that.
- **The skill exists and gets ignored.** Rewrite the description first, add the situations and the vocabulary they actually use, and re-run the trigger test. Only touch the body if it fires and then behaves badly.
- **The skill fires on the wrong requests.** The description is too broad or shares words with a neighbouring task. Name the boundary explicitly ("for transcripts, not for support tickets").
- **The job is genuinely long.** Split it: a main skill with the workflow, plus reference files per variant, each named in the body with a line about when to read it.
- **The output is subjective** (writing voice, visual taste). Skip the pass-or-fail testing and judge the outputs directly with the person; forcing a checkable assertion onto taste produces a worse skill.
## Files
- `references/examples.md` - three complete builds: a repeated task turned into a skill, a description rewritten until it triggered, and a skill split in two because it was doing two jobs. Read before drafting your first skill.
- `references/checklist.md` - the review pass to run before shipping a skill, plus the trigger-test procedure written out with example request sets. Read when a skill is drafted and you are deciding whether it is done.
- `scripts/validate_skill.py` - run it, do not read it: `python scripts/validate_skill.py path/to/skill-folder`. Checks frontmatter fields, the name format, description length, body size, and whether every bundled file is mentioned in the body. It checks mechanics only; it cannot tell you whether the skill is any good.
## Attribution
Adapted from the `skill-creator` skill in [anthropics/skills](https://github.com/anthropics/skills/tree/main/skills/skill-creator), Apache-2.0. The authoring loop, the progressive-disclosure model, the description-triggering guidance and the validation script come from there. The eval and benchmark tooling in the original is deliberately left out; the trigger test here is its lightweight equivalent.
references/checklist.md
# Review and trigger test
Run this before calling a skill finished. The first half is a read-through; the second half is the only reliable way to know whether the skill will ever load.
## The read-through
**Frontmatter**
- `name` is lowercase with hyphens, no version number, and matches the folder name exactly.
- `description` is third person and contains both halves: what it does, and when to use it.
- The description names the file types, tools, and phrases a person would actually use.
- The description is under 1024 characters and contains no angle brackets.
**Body**
- The first line after the title says what the assistant becomes when this skill is active.
- The workflow is numbered and each step is an action, not a feeling.
- There is at least one worked example with real input and the full output.
- The output shape is stated exactly: sections, order, length limits.
- There is guidance for thin, missing, or ambiguous input.
- Every rule has a reason attached, or is obviously self-explaining.
- No dates, prices, model names, or version numbers that will be wrong in six months.
- Under about 400 lines. Past that, move depth into `references/`.
**Bundled files**
- Every file in `references/` and `scripts/` is named in the body, with a line about when to read or run it.
- Paths are relative, forward slashes, one level deep.
- Scripts say whether to run them or read them. Most should be run and never read.
- No file exists that the body does not point at. An unmentioned file never loads.
**Honesty**
- The skill does what its description says and nothing else surprising.
- It does not instruct the assistant to hide steps from the person, or to claim work it did not do.
## The trigger test
A skill that reads beautifully and never activates is worth nothing. Test triggering on its own, before testing quality.
**Step 1: write ten requests.** Six or seven that should load the skill, three or four near-misses that should not.
Make them look like real messages. Concrete file names, a sentence of backstory, lowercase, abbreviations, the odd typo. Vary the length. A polished one-line request is not what anyone types.
Avoid trivial requests. An assistant handles "read this file" directly and will not consult a skill for it, so such a request tests nothing.
**Step 2: make the near-misses hard.** The valuable negative case shares vocabulary with the skill but needs a different job done. For a skill that cleans spreadsheets, "build a chart from this data" is a good negative; "write a poem" is not - it fails for reasons that have nothing to do with your description.
**Step 3: run each request in a fresh session** and record whether the skill loaded. Repeat the borderline ones two or three times; triggering is not perfectly deterministic and a single run can mislead.
**Step 4: read the failures as description gaps.**
| What happened | What it means | Fix |
| --- | --- | --- |
| Should have fired, did not | The situation is missing from the description | Add that situation and its vocabulary |
| Fired on a near-miss | The description is too broad | State the boundary: "for X, not for Y" |
| Fires inconsistently on the same request | The description is vague or overlaps another skill | Sharpen the distinguishing words |
**Step 5: re-run the whole set after any description change.** A rewrite that fixes one gap can open another, and only the full set will show it.
## The quality test
Separate from triggering, and only worth running once the skill fires.
1. Take a real task, not a sample one.
2. Start a fresh session with none of the context that lives in the author's head.
3. Give the request in the person's own words, and let the skill do the work.
4. Read the output next to what the person would have produced by hand.
Every difference is information. If the result is missing a constraint, that constraint belongs in the skill. If the result is right but the shape is wrong, the output section needs to be more specific. If the result surprises you, some assumption never made it out of the author's head and into the file.
Add the smallest instruction that fixes the case, then run it again. Do not rewrite the whole skill in response to one bad output; skills degrade when every failure produces a new paragraph.
## When to stop
Stop when a fresh session, given a real task, produces something the person would send without editing. That is the bar. Passing it once is enough to ship, and the next few real uses will tell you what to add.
references/examples.md
# Worked builds
Three skills built end to end: what the person asked for, what was written, and why it ended up shaped that way.
## Contents
- [Build 1: a repeated task becomes a skill](#build-1-a-repeated-task-becomes-a-skill)
- [Build 2: a skill that never fired](#build-2-a-skill-that-never-fired)
- [Build 3: one skill split into two](#build-3-one-skill-split-into-two)
- [Patterns worth copying](#patterns-worth-copying)
---
## Build 1: a repeated task becomes a skill
**Request.** "Every Friday I write a client update email from my notes and it takes 40 minutes. Can this be a skill?"
**What was asked before writing anything:**
1. Show me last Friday's email, and one you were happy with.
2. What goes in every single one, no matter the week?
3. What have you had to fix afterwards?
The answers did most of the work. Every email had four parts (what shipped, what slipped and why, what is next, anything I need from you). The recurring fix was tone: the drafts hedged, the client wanted the slip stated plainly in the first line of that section.
**The skill:**
```markdown
---
name: client-weekly-update
description: Turns a week of rough notes into a client update email with four fixed sections - shipped, slipped, next, and asks. Use on Friday notes, standup logs, or a list of finished and unfinished tickets when someone needs to send a weekly client or stakeholder update.
---
# Client Weekly Update
You write the Friday client email from raw notes. The client reads it in 30 seconds on a phone, so it is short, plainly worded, and never buries bad news.
## Workflow
1. Sort the notes into shipped, slipped, next, and asks. Anything that fits nowhere is probably internal noise - leave it out.
2. Write each shipped item as an outcome the client can see, not the work that produced it. "Invoices now export to CSV", not "finished the export ticket".
3. State each slip in its first clause, then the reason, then the new date. No warm-up.
4. Keep "next" to three items maximum. A longer list reads as a plan nobody will hold you to.
5. Make every ask a direct question with a name and a date attached.
## Output shape
Subject line, then four headed sections in the order above, each 1-4 lines. Under 200 words total. No greeting longer than one line and no sign-off paragraph.
## Rules
- Never soften a slip with "slight", "minor", or "just". The client decides what is minor.
- Do not invent a reason for a delay. If the notes do not say why, write the delay and ask the user for the reason.
- Keep their product names exactly as written in the notes.
```
**Why it is shaped this way.** The four sections came from their real emails, not from a template. The two rules are the two mistakes they had been fixing by hand every week - that is where rules should come from. Nothing about tone in the abstract; one concrete banned move instead.
---
## Build 2: a skill that never fired
**Request.** "I wrote a skill for cleaning up spreadsheet exports and it never gets used. I have to name it every time."
**The description as written:**
```markdown
description: Spreadsheet cleanup helper.
```
That is a title, not a description. It says nothing about when to use it, and it contains none of the words the person actually types when they have this problem.
**The rewrite:**
```markdown
description: Cleans up a messy spreadsheet or CSV export - fixes header rows, splits merged columns, normalizes dates and currency, and removes blank and duplicate rows - and reports what it changed. Use when someone shares an xlsx or csv from an export, a report download, or a client, and mentions messy data, broken columns, weird dates, duplicates, or "make this usable".
```
**The trigger test that confirmed it.** Ten requests, run in fresh sessions:
Should fire:
1. "my boss sent me this sales export, the dates are all over the place and there are two header rows, can you sort it out"
2. "here's a csv from our billing system, half the amounts have currency symbols and half don't"
3. "clean this up so I can pivot it"
4. "this spreadsheet has duplicates from three different exports merged together"
5. "the client sent an xlsx where the first three rows are a logo and a title, get it into a proper table"
6. "make this usable please" (with a file attached)
Should not fire:
7. "write a formula that gives me the running total in column D" (spreadsheet work, but not cleanup)
8. "build a chart from this data" (downstream of cleanup, different job)
9. "what does this pivot table tell me about Q3" (analysis, not cleaning)
10. "convert this csv to json" (a format change, no cleanup involved)
Before the rewrite, 1 of 6 fired. After, 6 of 6 fired and none of the near-misses did. The body of the skill was never touched.
**The lesson.** When a skill is ignored, the body is rarely the problem. The description is the entire trigger surface, and near-miss test cases are what prove the boundary is in the right place.
---
## Build 3: one skill split into two
**Request.** "My onboarding skill is 600 lines and the results are inconsistent."
Reading it showed two unrelated jobs stapled together: writing onboarding documentation for new hires, and provisioning their accounts and access. Different inputs, different outputs, different reader. The assistant was loading 600 lines to do either half.
**The split:**
| Skill | Job | Trigger words in the description |
| --- | --- | --- |
| `onboarding-doc-writer` | Writes the first-week guide for a role | new hire guide, onboarding doc, first week, ramp-up plan |
| `access-provisioning-runbook` | Produces the exact account and permission steps for a role | accounts, access, permissions, provisioning, day-one setup |
Each landed around 120 lines. The per-role detail that had bloated the original moved to `references/roles.md` in the second skill, with one line in the body: "Read `references/roles.md` for the tool list and permission set for the role in question."
**Why this fixed the inconsistency.** With both jobs in one file, half the instructions were irrelevant on any given run, and the assistant averaged them. Two focused files, each fully relevant when it loads, removed the ambiguity without changing a single instruction.
---
## Patterns worth copying
1. Start from a real example the person already produced, and generalize from it. Never from a blank page.
2. Rules come from past failures. If a rule does not correspond to something that actually went wrong, it is decoration.
3. The description gets its own test, separate from the output. Near-misses are the half of that test that teaches you something.
4. When a skill grows, split by job, not by length. Length is the symptom.
5. Say the output shape exactly. "Four headed sections, under 200 words" produces consistent results; "be concise" does not.
6. Keep the person's own vocabulary - their product names, their section titles. A skill that renames things gets rewritten by hand every time.
scripts/validate_skill.py
#!/usr/bin/env python3
"""Check the mechanics of a skill folder before you test it.
Adapted from the quick_validate.py script in anthropics/skills (Apache-2.0). This
version needs no third-party packages: the frontmatter is read line by line rather
than through a YAML parser, and the checks are extended to cover body length and
bundled files the body never mentions.
It checks mechanics only. It cannot tell you whether the skill is any good, and it
cannot tell you whether the description will trigger - that needs the trigger test.
Usage:
python scripts/validate_skill.py path/to/skill-folder
Exit codes:
0 no errors (warnings may still be printed)
1 at least one error
2 the path is unusable (missing folder, unreadable SKILL.md)
"""
import os
import re
import sys
ALLOWED_KEYS = {"name", "description", "license", "allowed-tools", "metadata", "compatibility"}
NAME_RE = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*$")
MAX_NAME = 64
MAX_DESCRIPTION = 1024
MAX_BODY_LINES = 500
BUNDLE_DIRS = ("references", "scripts", "assets")
EXIT_OK = 0
EXIT_ERRORS = 1
EXIT_UNUSABLE = 2
def split_frontmatter(text):
"""Return (frontmatter_lines, body_lines). Raises ValueError if the block is malformed."""
lines = text.replace("\r\n", "\n").split("\n")
if not lines or lines[0].strip() != "---":
raise ValueError('SKILL.md must start with a "---" line opening the frontmatter')
for index in range(1, len(lines)):
if lines[index].strip() == "---":
return lines[1:index], lines[index + 1:]
raise ValueError('the frontmatter block is never closed by a second "---" line')
def parse_fields(fm_lines):
"""Pull top-level `key: value` pairs. Nested keys (indented) are attributed to their parent."""
fields = {}
for line in fm_lines:
if not line.strip() or line.lstrip().startswith("#"):
continue
if line[:1] in (" ", "\t"):
continue # nested under the previous key, e.g. metadata
match = re.match(r"^([A-Za-z][A-Za-z0-9_-]*):\s*(.*)$", line)
if not match:
continue
key, value = match.group(1), match.group(2).strip()
if len(value) > 1 and value[0] == value[-1] and value[0] in "\"'":
value = value[1:-1]
fields.setdefault(key, value)
return fields
def list_bundled(folder):
"""Every file under references/, scripts/ and assets/, as forward-slash relative paths."""
found = []
for sub in BUNDLE_DIRS:
base = os.path.join(folder, sub)
if not os.path.isdir(base):
continue
for root, _dirs, names in os.walk(base):
for name in sorted(names):
if name.startswith("."):
continue
full = os.path.join(root, name)
rel = os.path.relpath(full, folder).replace(os.sep, "/")
found.append(rel)
return found
def check(folder):
"""Return (errors, warnings) for one skill folder."""
errors, warnings = [], []
skill_md = os.path.join(folder, "SKILL.md")
try:
with open(skill_md, "r", encoding="utf-8") as handle:
text = handle.read()
except OSError as exc:
raise SystemExit(f"cannot read {skill_md}: {exc}")
try:
fm_lines, body_lines = split_frontmatter(text)
except ValueError as exc:
return [str(exc)], warnings
fields = parse_fields(fm_lines)
unexpected = sorted(set(fields) - ALLOWED_KEYS)
if unexpected:
errors.append(
"unexpected frontmatter key(s): " + ", ".join(unexpected)
+ ". Allowed: " + ", ".join(sorted(ALLOWED_KEYS))
)
name = fields.get("name", "")
if not name:
errors.append('frontmatter has no "name"')
else:
if not NAME_RE.match(name):
errors.append(f'name "{name}" must be lowercase words joined by single hyphens')
if len(name) > MAX_NAME:
errors.append(f"name is {len(name)} characters, over the {MAX_NAME} limit")
folder_name = os.path.basename(os.path.abspath(folder))
if name != folder_name:
warnings.append(f'name "{name}" does not match the folder name "{folder_name}"')
description = fields.get("description", "")
if not description:
errors.append('frontmatter has no "description" - the skill will never trigger')
else:
if "<" in description or ">" in description:
errors.append("description contains an angle bracket")
if len(description) > MAX_DESCRIPTION:
errors.append(f"description is {len(description)} characters, over the {MAX_DESCRIPTION} limit")
if len(description) < 60:
warnings.append(
f"description is only {len(description)} characters - it probably says what the "
"skill does but not when to use it"
)
if description.lower().startswith(("i ", "this skill")):
warnings.append("description should be third person and lead with the verb")
while body_lines and body_lines[-1].strip() == "":
body_lines.pop()
if len(body_lines) < 10:
errors.append(f"the body is {len(body_lines)} lines - there is no instruction here yet")
elif len(body_lines) > MAX_BODY_LINES:
warnings.append(
f"the body is {len(body_lines)} lines, over the {MAX_BODY_LINES} guideline - "
"move depth into references/"
)
body_text = "\n".join(body_lines)
for rel in list_bundled(folder):
if rel not in body_text:
warnings.append(f'"{rel}" is never mentioned in the body, so it will never be loaded')
if "\\references\\" in body_text or "\\scripts\\" in body_text:
errors.append("the body uses backslash paths - bundled files must use forward slashes")
return errors, warnings
def main(argv):
if len(argv) != 1:
print("usage: python scripts/validate_skill.py path/to/skill-folder", file=sys.stderr)
return EXIT_UNUSABLE
folder = argv[0]
if not os.path.isdir(folder):
print(f"not a directory: {folder}", file=sys.stderr)
return EXIT_UNUSABLE
try:
errors, warnings = check(folder)
except SystemExit as exc:
print(str(exc), file=sys.stderr)
return EXIT_UNUSABLE
for message in errors:
print(f"ERROR {message}")
for message in warnings:
print(f"WARN {message}")
if errors:
print(f"\n{len(errors)} error(s), {len(warnings)} warning(s)")
return EXIT_ERRORS
print(f"\nmechanics look fine ({len(warnings)} warning(s)). Now run the trigger test.")
return EXIT_OK
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
Install it
Claude Code (this project)
Unzip the download into .claude/skills/. The folder is already named right.
Claude Code (every project)
Unzip into ~/.claude/skills/ to use it everywhere.
claude.ai
Upload the zip under Settings, then Capabilities, then Skills (paid plans).
How to call it
Just describe the task. The description line inside the skill does the matching. Or call it directly:
/write-and-call-skillshelp me write a skillhow do skills actually workturn this into a skill I can reusemy skill never activates, whymake this repeated task automatic
Works beyond Claude
Skills use the open Agent Skills format. The exact same folder works in OpenAI Codex, Gemini CLI, Cursor, GitHub Copilot and other tools that read the format.
Using a plain chat app? Paste SKILL.md into ChatGPT or Gemini as instructions. You lose auto-triggering, but the method still works.