cheat sheet

Claude Code Skills

Author and load custom Claude Code skills — how skills are discovered, the SKILL.md schema, available-skills list, packaging, project-vs-user scope, plugin namespacing, and the difference from slash commands.

Claude Code Skills

What it is

Skills are packaged units of Claude Code customisation — a folder containing a SKILL.md (instructions and metadata), optional scripts, and any reference files the skill needs to operate. Unlike slash commands, which just inject a Markdown body into the conversation, skills are surfaced via the Skill tool: Claude sees them in a structured "available-skills" list, decides when to load one, and the harness streams the skill's content (including bundled files) into the context only at that moment. They are the recommended way to ship reusable, retrieval-biased capabilities — domain knowledge, multi-step workflows, framework-specific recipes — without bloating every session's system prompt.

How skills differ from slash commands

Both extend Claude Code with reusable behavior, but they have different load semantics and use cases.

Slash commandSkill
Storage.claude/commands/<name>.md.claude/skills/<name>/SKILL.md
DiscoveryTab-complete /<name>Auto-listed in available-skills
InvocationUser types /<name>Claude calls Skill(name=...)
Body loadAlways inlined when typedLazy — loaded only when invoked
Bundled filesNoYes (any number)
Best forQuick repeatable promptsDomain knowledge + scripts + assets

A rule of thumb: if the work is a single user message, write a slash command. If the work needs reference material, helper scripts, or multi-file context, write a skill.

Discovery and the available-skills list

At session start, Claude Code scans ~/.claude/skills/, .claude/skills/, and any plugin-registered skill directories. For each skill it reads only the frontmatter and the description field — never the body — and injects a compact line into the system prompt:

text
- skill-name: <description from frontmatter>

This is the available-skills list. Claude sees it and can invoke any of them via the Skill tool. The body of each skill — the instructions Claude will actually follow — is only loaded into context when Claude decides to invoke it. This means you can ship dozens of skills without paying their token cost on every turn.

text
> /help skills

Output:

code
Available skills:
  - ship: Stage, commit, and push current changes.
  - create-article: Author a cheat-sheet article following project conventions.
  - verify: Run the app and observe behavior to confirm a change works.
  - code-review: Review the current diff for correctness bugs.
  - export-session: Save the current session and project memory to a snapshot.

Authoring a custom skill

A minimal skill is a folder containing one SKILL.md with required frontmatter and a body of instructions.

Project layout

text
.claude/skills/ship/
├── SKILL.md           # required — metadata + instructions
├── README.md          # optional — human docs
└── helpers/
    └── prepare.sh     # optional — bundled scripts

Output: (none — folder layout)

SKILL.md schema

The frontmatter declares the skill to Claude Code. Required fields are name and description. Everything else is optional.

markdown
---
name: ship
description: "Stage all changed content files, generate a commit message, commit, and push to the remote."
trigger: when the user types /ship, or asks to ship/commit-and-push the current changes
allowedTools: [Bash(git add *:git commit *:git push:git status:git diff)]
model: claude-haiku-4-5-20251001
---

# /ship

You are running the `ship` skill. Follow these steps in order:

1. Run `git status` to see what is changed.
2. Stage only files under `src/content/sections/` and `public/icons/`.
3. Generate a one-line commit message that summarises the changes.
4. Run `git commit -m "<message>"` (do not include Co-Authored-By).
5. Run `git push`.
6. Report the commit SHA and the pushed branch.

If any step fails, stop and report the error verbatim.

Frontmatter keys

KeyTypeRequiredEffect
namestringyesSkill identifier; must match folder name
descriptionstringyesShown in available-skills list; biases when Claude picks it
triggerstringnoFree-text hint about when to use; included in the list
allowedToolslistnoRestrict tools while this skill is active
disallowedToolslistnoBlock tools while this skill is active
modelstringnoForce a specific model while this skill runs
subagentstringnoRun this skill via a named subagent type
versionstringnoFree-form version label
markdown
---
name: code-review
description: "Review the current diff for correctness bugs."
allowedTools: [Read, Bash(git diff:git show)]
model: claude-opus-4-7
---

You are reviewing the diff. Produce a Markdown table:
| Severity | File:Line | Issue | Fix |

Limit yourself to real correctness bugs; ignore style.

Invocation

Skills are invoked one of three ways: via the Skill tool (Claude's choice, based on description match), via a slash command of the same name if the harness exposes one, or via a user instruction that explicitly names the skill.

By name

text
> /ship

Output:

text
[Skill: ship]
  git status → 3 files modified
  git add src/content/sections/claude-code/skills.md ...
  git commit -m "Add skills cheat sheet"
  git push origin main
[Skill done]
Commit a3f12c on main pushed.

Claude decides

If you say something that matches a skill's description or trigger, Claude picks it up.

text
> Commit and push the changes I just made.

Output:

text
[Skill: ship]   (matched on "commit and push")
...

Make description and trigger specific. Claude's skill-selection logic compares the user request to these fields; vague descriptions cause the wrong skill to fire.

Bundled files

A skill can ship arbitrary files alongside SKILL.md. They're not loaded into context automatically — the skill body instructs Claude to read them when needed. This pattern is how you ship reference material (e.g. a 2,000-line API spec) without polluting the system prompt.

text
.claude/skills/api-helper/
├── SKILL.md
├── api-spec.yaml         # OpenAPI 3.1 schema
├── examples/
│   ├── create-user.json
│   └── list-users.json
└── scripts/
    └── validate.py

Output: (none — folder layout)

markdown
---
name: api-helper
description: "Generate code that calls the Acme API. Validates against the bundled OpenAPI spec."
---

You are helping the user write code that calls the Acme API.

Before generating code:
1. Read `api-spec.yaml` in this skill folder for the canonical schema.
2. If the user asks about an endpoint not in the spec, refuse and say so.

Useful example requests live in `examples/`. Read them on demand.

After generating code, run `scripts/validate.py <generated-file>` to typecheck
the call against the spec.

The harness resolves paths inside SKILL.md relative to the skill folder, so api-spec.yaml resolves to .claude/skills/api-helper/api-spec.yaml.

Scope: project vs user vs plugin

Skills live at three scopes, in order of increasing priority:

ScopePathWhen
User-global~/.claude/skills/<name>/SKILL.mdPersonal — available everywhere
Project.claude/skills/<name>/SKILL.mdProject — committed for the team
Plugin~/.claude/plugins/<plugin>/skills/<name>/SKILL.mdInstalled via plugin

A project skill with the same name as a user skill overrides the user skill while you're in that project. Plugin skills are namespaced as plugin:skill (e.g. cloudflare:wrangler) to avoid collisions; they appear in the list with the namespace prefix and you invoke them by the full prefixed name.

text
> /help skills

Output:

text
Personal:
  - ship
  - export-session
Project (jockey):
  - create-article
  - ship          ← overrides personal
Plugins:
  - cloudflare:wrangler
  - claude-notifications-go:init

Tool restrictions

allowedTools and disallowedTools apply only while the skill is active — once it returns, normal session permissions resume. Use this to make destructive skills safer.

markdown
---
name: cleanup-dist
description: "Delete the build artifacts under dist/."
allowedTools: [Bash(rm -rf dist/*)]
---

Run `rm -rf dist/*` and confirm by listing the directory afterwards.
text
> /cleanup-dist

Output:

text
[Skill: cleanup-dist]
  rm -rf dist/*
  ls dist/ → (empty)
[Skill done]

The skill cannot run any other Bash command while active — even if Claude would normally have broad Bash permission — because allowedTools narrows the surface for the skill's lifetime.

Running a skill via a subagent

Set the subagent frontmatter key to run the skill body inside an isolated subagent. The parent's context is unaffected; only the final summary returns. Pair with worktree isolation for destructive skills.

markdown
---
name: spike-typescript
description: "Throwaway spike: convert a JS file to TypeScript in a worktree."
subagent: general-purpose
isolation: worktree
allowedTools: [Read, Edit, Write, Bash(npx tsc *)]
---

Convert the file named in $ARGUMENTS to TypeScript. Run `npx tsc --noEmit` to
verify. Return the resulting code. Do not commit.

See Subagents for the full isolation matrix.

Plugin skills

Plugins are packages installed under ~/.claude/plugins/ that can ship multiple skills, commands, and MCP servers in one bundle. Their skills appear in the available-skills list with a plugin: prefix.

bash
# Install a plugin (hypothetical)
claude plugin install anthropic/cloudflare

Output:

text
Installed 4 skills:
  cloudflare:wrangler
  cloudflare:workers-best-practices
  cloudflare:durable-objects
  cloudflare:cloudflare-email-service

Plugin skills behave exactly like user/project skills — same SKILL.md schema — they're just namespaced and installed atomically.

Skill triggering protocol

When Claude Code starts a turn, it has the available-skills list in the system prompt. The harness asks Claude to evaluate whether the user request matches a skill before responding. If a skill fires, the harness:

  1. Loads SKILL.md body into the conversation as a <command-name> block (showing the skill's instructions).
  2. Applies any frontmatter overrides (allowedTools, model, subagent).
  3. Lets Claude continue with the skill's instructions in scope.
  4. On completion, restores the previous tool permissions and model.

The skill's body is only loaded into context for that turn (or for the duration of the skill's subagent, if used). It does not persist into the rest of the session.

If your skill needs Claude to remember something for the rest of the session, have the skill body include "Remember: ..." or write to a memory MCP server. Skill bodies themselves do not stick.

Debugging skills

List what Claude sees

text
> /help skills

Output:

text
Personal:
  - ship: Stage, commit, push.
  - create-article: Author a cheat-sheet article.

If a skill is missing from the list, the harness rejected its frontmatter. Common causes: missing name, invalid YAML, folder name doesn't match name.

Force a skill to fire

text
> Invoke the skill named "ship".

Output:

text
[Skill: ship]
...

The explicit form bypasses Claude's matching heuristic, which is useful for testing.

Dry-run

Some skills support a dry-run convention — open the body in your editor and look for a $DRY_RUN flag. The harness has no built-in dry-run, but a well-written skill includes one.

markdown
---
name: ship
description: "..."
---

If $ARGUMENTS contains "--dry-run", print the proposed commit message and
git status, but do NOT run git commit or git push.

Common pitfalls

  1. Folder name vs name mismatch — the skill is registered by folder name; if SKILL.md's name: field disagrees, the harness logs a warning and uses the folder name.
  2. Long descriptionsdescription is shown in every session's system prompt; keep it under ~25 words.
  3. Body assumes context that isn't there — when a skill runs, the session conversation is still present but the harness may not feed in earlier turns; have the skill body read files explicitly rather than relying on "the file the user just mentioned".
  4. Tool grants too narrowallowedTools overrides the session's allow list entirely; if your skill needs Read and you only allow Bash, it can't read files.
  5. Bundled scripts not executablechmod +x scripts/*.sh before committing; otherwise the skill's Bash invocations fail silently.
  6. Skill collisions across scopes — a personal ship and a project ship both compile; the project version wins. Rename or document the override.
  7. Skills with side effects in description triggering accidentally — say "Delete all caches without confirmation" in a description and Claude will eventually pick it up at the wrong moment. Use phrases like "when the user asks to ..." in trigger to gate it.

Real-world recipes

Project-scoped /ship

A team-wide commit-and-push skill so every contributor uses the same flow.

markdown
---
name: ship
description: "Stage content files, commit, and push to main."
allowedTools: [Bash(git add *:git commit -m *:git push:git status:git diff --stat)]
---

1. git status — show working tree state.
2. git add src/content/ public/icons/
3. git diff --staged --stat → propose a one-line commit message.
4. git commit -m "<msg>"
5. git push origin main
6. Report commit SHA + remote.

create-article cheat-sheet helper

A skill that authors a new cheat-sheet article following the project's conventions, bundled with the YAML schema.

text
.claude/skills/create-article/
├── SKILL.md
├── schema.yaml
└── examples/
    └── grep.md

Output: (none — folder layout)

markdown
---
name: create-article
description: "Author a cheat-sheet Markdown file with valid frontmatter."
allowedTools: [Read, Write, Bash(python scripts/validate_frontmatter.py)]
---

1. Read schema.yaml for the required frontmatter fields.
2. Read examples/grep.md as a structural template.
3. Generate src/content/sections/<section>/<slug>.md following the schema.
4. Run python scripts/validate_frontmatter.py — fix any errors.
5. Report the path and word count.

create-concept tag-to-page promoter

A skill that synthesizes a cross-cutting tag into a concept page under src/content/concepts/<slug>.md, with the five required H2s (## Definition / ## Why it matters / ## How it works / ## Common pitfalls / ## Where to go next).

text
.claude/skills/create-concept/
├── SKILL.md
└── schema.yaml

Output: (none — folder layout)

markdown
---
name: create-concept
description: "Promote a cross-cutting tag into a synthesizing concept page."
allowedTools: [Read, Write, Bash(npm run concepts:expand -- *:npm run concepts:audit*)]
---

1. Run `npm run concepts:expand -- <slug> --out=/tmp/<slug>-brief.md` to gather excerpts from existing tagged articles.
2. Read schema.yaml for the concept frontmatter (no `section:` / `categories:`; required `aliases:`).
3. Draft src/content/concepts/<slug>.md with the five required H2s in order.
4. Run `npm run concepts:audit` — confirm the slug is no longer flagged `unfleshed` or `thin`.
5. Report the path and word count.

update-concept staleness refresher

A skill that refreshes existing concept pages — single slug, every stale-flagged page, or every concept with --all.

markdown
---
name: update-concept
description: "Refresh stale concept pages (single, batch-stale, or --all)."
allowedTools: [Read, Edit, Bash(npm run concepts:audit*:npm run concepts:expand -- *)]
---

1. Resolve mode from $ARGUMENTS: `<slug>` | (none → batch-stale) | `--all`.
2. Run `npm run concepts:audit` to read the current flag set.
3. For each target slug, run `concepts:expand` for fresh excerpts and re-draft any of the five H2s that have drifted.
4. Bump the `updated:` field and append the article to scripts/data/web-research-log.json (unless `--all`, which bypasses the 180-day throttle).
5. Report which concepts were refreshed and which remain.

Bundled domain knowledge

For a complex domain (e.g. Stripe APIs), ship the API reference inline.

text
.claude/skills/stripe-helper/
├── SKILL.md
├── api-reference.md       # 3,000 lines, never loaded by default
└── code-examples/
    ├── create-payment.py
    └── webhook-handler.py

Output: (none — folder layout)

The 3,000-line reference costs zero tokens until Claude invokes the skill and the body says "for any non-trivial question, read api-reference.md before answering." Then those tokens are spent only for that turn.

Personal /export-session

A user-global skill that saves the current session and CLAUDE.md to a timestamped folder.

markdown
---
name: export-session
description: "Export the current session context and project memory to a snapshot folder."
allowedTools: [Bash(mkdir -p *:cp *:tar *)]
---

1. Make ~/.claude/snapshots/<date>-<session-id>/
2. Copy ~/.claude/projects/<hash>/<session>.jsonl into it
3. Copy any CLAUDE.md files
4. Tar the folder
5. Report the tarball path

Skill as a subagent + worktree

Combine subagent: general-purpose and isolation: worktree to ship a "throwaway spike" skill.

markdown
---
name: spike
description: "Try a risky refactor in a git worktree, without touching main."
subagent: general-purpose
isolation: worktree
---

Implement the change described in $ARGUMENTS inside the worktree. Run tests.
Return the diff. Do not commit. The user will decide whether to merge.

Invoke with /spike convert utils.js to TypeScript.

Output:

text
[Skill: spike, subagent, worktree]
  edits, tests, returns diff
[Skill done]