# How to Create a Claude Skill: Step-by-Step Guide

> AI Release · @ai_release1 · https://ai-release.org/guides/kak-sozdat-claude-skill-poshagovoe-rukovodstvo-po-svoim_en.html

## TL;DR
- **Time to complete:** 20–30 minutes for a first skill that ships one script.
- **Difficulty:** Intermediate — you need to be comfortable with a terminal, YAML frontmatter, and ZIP archives.
- **You will need:** a text editor, the `zip`/`unzip` CLI (or PowerShell `Compress-Archive`), Python 3 only if your skill runs a script, and a Claude plan that exposes the Skills feature — open **Settings → Capabilities → Skills** first to confirm the entry exists in your workspace (the exact menu label differs between the web app, desktop app, and admin console — **verify against the documentation** for your plan).

A skill is a folder containing a `SKILL.md` file plus optional scripts and reference files. Claude reads three layers on demand: the frontmatter metadata first, the Markdown body only when the skill looks relevant, and bundled files only when the body tells it to open them. That layering is why the `description` field decides whether your skill ever runs.

## Requirements and setup
![Illustration: Requirements and setup](https://ai-release.org/guides/img/kak-sozdat-claude-skill-poshagovoe-rukovodstvo-po-svoim-en-1.jpg)

- **OS:** macOS 13+, Windows 11, or a Linux distribution with a POSIX shell. Windows users can run every command below in WSL or replace `zip` with `Compress-Archive`.
- **Archive tooling:** `zip` and `unzip` installed (`zip -v` prints a version banner).
- **Python:** version 3.9 or newer, only if your skill ships a script. Check with `python3 --version`; on Windows use `py --version`.
- **Claude access:** a workspace where the Skills capability is enabled. If the **Skills** entry is missing from settings, the feature is not enabled for your account — **verify against the documentation** rather than guessing an endpoint.
- **Disk:** a few hundred KB is enough; skills are text folders, not models.
- **No API keys inside the skill.** Anything you bundle is readable by the model when it loads the file. Keep secrets in the client's own configuration, not in `SKILL.md`.

Structure you are going to build:

| Path | Required | Purpose |
|---|---|---|
| `<skill-name>/SKILL.md` | yes | YAML frontmatter + instructions |
| `<skill-name>/scripts/` | no | executable helpers the body can call |
| `<skill-name>/references/` | no | long docs loaded only on demand |

Pre-flight checklist:

- [ ] `zip -v` and `unzip -v` both print output
- [ ] `python3 --version` prints `3.9` or higher (skip if no script)
- [ ] The **Skills** entry is visible in your Claude settings
- [ ] You have a scratch folder such as `~/skills-work` for building archives

If you plan to test a skill against a service running on your own hardware, set that service up first — the workflow in [How to add AI to your home server: self-hosted services guide](https://ai-release.org/guides/kak-podklyuchit-ii-k-domashnemu-serveru-poshagovoe-ruko.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) gives you a local endpoint you can call from a bundled script.

## Step 1. Design the skill and write a trigger description
**What to do:** Pick one narrow job, name the skill in lowercase-hyphen form, and write a `description` that lists the situations where the skill must fire.

**Why:** The description is the only part of your skill that is always in context. If it does not contain the words a user actually types — file extensions, verbs, domain nouns — Claude has no reason to open the body. A vague description is the single most common reason a correct skill never activates.

**How to check:** Create the folder and file, then confirm the frontmatter parses.

```bash
mkdir -p ~/skills-work/csv-sanity-check/scripts
cd ~/skills-work/csv-sanity-check
```

Create `SKILL.md` with this frontmatter. The `name` value must match the folder name exactly, and the description stays on one line so the YAML parser never sees a broken key:

```markdown
---
name: csv-sanity-check
description: Audits an uploaded CSV file for empty cells, duplicate rows, and rows whose column count does not match the header, then returns a short report. Use when the user uploads a .csv file and asks whether the data is clean, complete, or safe to load into a database.
---

# CSV sanity check

## When to use
Use this skill when the user supplies a `.csv` file and asks about data quality,
completeness, or duplicates.

## Steps
1. Save the uploaded file in the working directory as `input.csv`.
2. Run `python3 scripts/check.py input.csv`.
3. Paste the script output verbatim into the reply, then add one sentence naming the worst issue.

## Output rules
- Always report: row count, column count, empty cells, duplicate rows, ragged rows.
- Never estimate numbers. Copy them from the script output.
```

**Example:** the phrase `Use when the user uploads a .csv file` is what makes the skill fire on "here's my export, is it clean?" — a description saying only "helps with data" would not match that sentence.

Run the frontmatter lint:

```bash
python3 - <<'PY'
import pathlib, re
text = pathlib.Path("SKILL.md").read_text(encoding="utf-8")
m = re.match(r"^---\n(.*?)\n---\n", text, re.S)
assert m, "frontmatter block missing"
fm = m.group(1)
for key in ("name:", "description:"):
    assert key in fm, f"missing {key}"
print("frontmatter OK")
PY
```

Expected output: `frontmatter OK`. Anything else means the `---` fences or the lowercase keys are wrong — fix that before continuing, because every later step depends on the folder name and the `name` field agreeing.

## Step 2. Add the script, test it locally, and build the archive
![Illustration: Step 2. Add the script, test it locally, and build the archive](https://ai-release.org/guides/img/kak-sozdat-claude-skill-poshagovoe-rukovodstvo-po-svoim-en-2.jpg)

**What to do:** Write the helper your body calls, run it against a fixture, then zip the folder so that `SKILL.md` sits at the archive root.

**Why:** Claude executes the script inside a sandbox that has the Python standard library but not your machine's installed packages and not your network. Testing locally with the standard library only is the cheapest way to catch that mismatch before upload.

**How to check:** The script must print the same five lines locally as it will inside Claude, and the archive listing must show `SKILL.md` at depth one.

Create `scripts/check.py`:

```python
#!/usr/bin/env python3
import csv, sys
from collections import Counter

def main(path):
    with open(path, newline="", encoding="utf-8") as f:
        rows = list(csv.reader(f))
    if not rows:
        print("rows: 0")
        return
    header, body = rows[0], rows[1:]
    ragged = sum(1 for r in body if len(r) != len(header))
    empty = sum(1 for r in body for c in r if c.strip() == "")
    dupes = sum(n - 1 for n in Counter(tuple(r) for r in body).values() if n > 1)
    print(f"rows: {len(body)}")
    print(f"columns: {len(header)}")
    print(f"empty_cells: {empty}")
    print(f"duplicate_rows: {dupes}")
    print(f"ragged_rows: {ragged}")

if __name__ == "__main__":
    main(sys.argv[1])
```

Create a fixture `sample.csv`:

```csv
id,name,email
1,Ann,ann@example.com
2,Bob,
2,Bob,
```

Run it:

```bash
python3 scripts/check.py sample.csv
```

Expected output:

```
rows: 3
columns: 3
empty_cells: 1
duplicate_rows: 1
ragged_rows: 0
```

Build the archive from the parent directory, not from inside the skill folder — this is what keeps the paths flat:

```bash
cd ~/skills-work
zip -r csv-sanity-check.zip csv-sanity-check -x '*.DS_Store' -x '__pycache__/*'
unzip -l csv-sanity-check.zip
```

Expected listing contains exactly `csv-sanity-check/SKILL.md` and `csv-sanity-check/scripts/check.py`. If you see `csv-sanity-check/csv-sanity-check/SKILL.md`, you zipped the folder from the wrong directory and the upload will fail.

Upload it in the Claude client: open **Settings → Capabilities → Skills**, choose the upload action, and select `csv-sanity-check.zip` (menu labels vary by client — **verify against the documentation**). For Claude Code, the equivalent is copying the folder to `~/.claude/skills/csv-sanity-check/`; confirm the skills directory path for your version in the documentation.

## Step 3. Verify that the skill triggers, runs, and stays quiet
![Illustration: Step 3. Verify that the skill triggers, runs, and stays quiet](https://ai-release.org/guides/img/kak-sozdat-claude-skill-poshagovoe-rukovodstvo-po-svoim-en-3.jpg)

**What to do:** Run three prompts — one that must trigger, one that must not, one that should trigger but lacks the file.

**Why:** A skill that never fires and a skill that fires on everything are both broken. You need evidence for both directions before you hand the skill to anyone else.

**How to check:** Attach `sample.csv` and send:

```
Check this CSV before I load it into Postgres: is it clean?
```

Success looks like: the reply names the skill (the client shows a skill/tool indicator above the message), the five script lines appear verbatim, and the closing sentence names the empty `email` cell or the duplicated Bob row. Because the body explicitly forbids estimating, a reply without the literal `duplicate_rows: 1` line means the script did not execute.

Negative test — no attachment, no CSV:

```
Summarise this article for me: <paste three paragraphs of prose>
```

Expected: the skill indicator is absent. If it appears, your description is too broad; add a constraint such as `Use only when a .csv file is attached.`

Third test — the script's error path. Rename the fixture so the path is wrong and confirm the model reports the failure instead of inventing numbers:

```bash
mv sample.csv missing.csv
```

Ask the same question with the file attached. Expected: the reply quotes the Python error and asks for a valid file. This proves the instructions are being followed rather than hallucinated.

Acceptance checklist before you publish:

- [ ] Trigger prompt shows the skill indicator and the five metric lines
- [ ] Prose prompt shows no skill indicator
- [ ] Missing-file prompt surfaces the real error text
- [ ] Reply never contains a number that is absent from the script output

If the skill will call an internal monitoring endpoint, the polling pattern in [How to Set Up VPS Monitoring via a Telegram Bot with Mini App](https://ai-release.org/guides/kak-nastroit-monitoring-vps-cherez-telegram-bota-s-mini.html?utm_source=tg&utm_medium=channel&utm_campaign=guide_inline&utm_content=guide_to_guide) gives you a stable JSON payload to parse with `urllib.request` from inside `scripts/`.

## Troubleshooting

**Problem. The upload is rejected or the skill appears with no instructions.**
- Error: `No SKILL.md found at the root of the archive`
- Cause: the ZIP contains an extra directory level, usually because you ran `zip -r out.zip ~/skills-work/csv-sanity-check` with an absolute path.
- Solution: rebuild from the parent folder with a relative path and re-check the listing.
```bash
cd ~/skills-work && rm -f csv-sanity-check.zip
zip -r csv-sanity-check.zip csv-sanity-check -x '*.DS_Store'
unzip -l csv-sanity-check.zip | grep SKILL.md
```
The output must be `csv-sanity-check/SKILL.md`, with no repeated folder name. Re-upload the rebuilt archive and delete the broken copy from the skills list.

**Problem. The skill fires but the script dies instead of returning numbers.**
- Error: `ModuleNotFoundError: No module named 'requests'`
- Cause: the script imports a third-party package. The execution sandbox ships the Python standard library, not your virtualenv, and outbound network access is not guaranteed.
- Solution: replace the dependency with `urllib.request` or `csv`/`json` from the standard library, then retest locally with a clean interpreter before re-zipping.
```bash
python3 -I scripts/check.py sample.csv
```
`-I` runs in isolated mode and ignores site-packages, which mimics the sandbox. If it prints the five metric lines under `-I`, it will run inside Claude.

**Problem. The skill is listed but never activates.**
- Error: `AssertionError: missing description:` printed by the lint step, or activation simply never happens when the YAML is valid but the wording is generic.
- Cause: either a capitalised or misspelled key (`Description:`) breaks the frontmatter, or the description names the capability instead of the user's phrasing.
- Solution: keep both keys lowercase, keep the description on one line, and add the literal words the user types (`.csv`, "clean", "duplicates"). Re-run the lint, rebuild the archive, re-upload, then repeat the trigger prompt from Step 3.

## Summary and next steps
You built a skill folder with a trigger-focused description, a standard-library script, and a flat ZIP that uploads cleanly. Next, clone the same layout for a second skill that references a long document under `references/` so the body stays short and the file loads only on demand.

## FAQ

**What is the difference between a Claude Skill and a saved prompt?**
A prompt is text you paste every time; a skill is a folder that stays installed, with metadata that lets Claude decide on its own when to load the instructions and any bundled scripts.

**Does a skill have to contain code?**
No. A `SKILL.md` with instructions and examples is a complete skill. Add `scripts/` only when you need deterministic output such as counts, parsing, or a formatted report.

**Where do skills live when I use Claude Code?**
As a folder under the skills directory, for example `~/.claude/skills/csv-sanity-check/SKILL.md`. Confirm the exact directory and precedence rules for your installed version in the documentation, since project-level and user-level locations can both apply.

**How do I update a skill that is already installed?**
Edit `SKILL.md` or the scripts locally, rebuild the ZIP with the same commands, remove the old copy from the skills list, and upload the new archive. Re-run the three Step 3 prompts afterwards — an updated description is the usual reason a previously working trigger stops firing.
