All sections

06Extending

Skills

A directory with a SKILL.md, where the directory name becomes the slash command. Only the description sits in context permanently, so a four-hundred-line reference costs almost nothing until the moment it is needed.

What a skill is

A directory containing a SKILL.md: YAML frontmatter plus markdown body. The directory name becomes the slash command, so skills/api-review/ gives you /api-review.

The defining property is progressive disclosure. Only the description sits in context permanently. The body loads when the skill is invoked, either by you typing the command or by Claude matching the description against your prompt.

flowchart LR
    A["Session starts"] --> B["All skill descriptions<br/>loaded into context<br/><i>~1 line each</i>"]
    B --> C{"User prompt or<br/>Claude's judgement<br/>matches a description?"}
    C -->|"No"| D["Body never loads<br/><i>zero token cost</i>"]
    C -->|"Yes"| E["Full SKILL.md body<br/>injected into context"]
    E --> F["Dynamic commands run<br/>output substituted inline"]
    F --> G["Claude executes the<br/>instructions"]

Anatomy

---
name: api-review
description: Reviews HTTP handler changes against our API conventions. Use when reviewing endpoints, request/response shapes, or error handling in the API layer.
allowed-tools: Bash(git diff *) Bash(git status *)
---

## Current diff

!`git diff HEAD -- ':(glob)**/handlers/**'`

## Review checklist

Examine the diff above for:

1. **Error shape** — every error path returns the standard envelope,
   not a bare string.
2. **Validation** — request bodies validated before any downstream call.
3. **Idempotency** — POST handlers that create resources accept an
   idempotency key.
4. **Observability** — new handlers register a latency histogram
   and an error counter.

Report findings grouped as Critical / Warning / Suggestion, each with a
`file:line` reference. If the diff is empty, say so and stop.

Frontmatter fields

Field Purpose
name Metadata. The command name comes from the directory name, not this field
description What Claude matches against your prompt. Truncated in the listing at 1,536 characters, so front-load the primary use case
allowed-tools Pre-approved tools for the duration of the skill, avoiding permission prompts
disable-model-invocation true = only you can run it. Also removes the description from context entirely
user-invocable false = only Claude can load it; no slash command is created
model Override the model for this skill’s turn

When to set disable-model-invocation: true: anything with side effects. /deploy, /commit, /release. You do not want Claude deciding on its own that now is a good moment to deploy.

When to set user-invocable: false: background reference material that isn’t a meaningful user action. “How our event schema versioning works” is knowledge, not a command.

Writing a description that actually gets matched

The description is the entire matching surface. These two are the same skill:

Description Auto-invoked?
Reviews API code. Rarely. Too vague to beat Claude’s default behaviour
Reviews HTTP handler changes against our API conventions. Use when reviewing endpoints, request/response shapes, error envelopes, or idempotency in the API layer. Reliably

Front-load the trigger, then enumerate the concrete nouns someone would actually type. “Use when…” phrasing works well because it describes the situation rather than the skill.

Dynamic context injection

The !`command` syntax runs a shell command and substitutes its output before Claude sees the skill. The instructions arrive with real data already inlined, rather than Claude having to go fetch it.

## Repository state

Branch:  !`git rev-parse --abbrev-ref HEAD`
Status:  !`git status --short`
Recent:  !`git log --oneline -10`

This is the single highest-leverage feature of skills. It converts “Claude, go find out X, then do Y” into “here is X, do Y”, removing an entire round of tool calls and an entire opportunity for the model to look in the wrong place.

Pair it with allowed-tools so the commands run without prompting.

More worked skills

/release-notes

---
name: release-notes
description: Drafts release notes from the commits since the last tag. Use when cutting a release, preparing a changelog, or summarising what shipped.
allowed-tools: Bash(git log *) Bash(git describe *) Bash(git tag *)
---

Last tag:  !`git describe --tags --abbrev=0`
Commits:   !`git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges`

Draft release notes from the commits above.

- Group under **Added**, **Changed**, **Fixed**, **Internal**.
- One line each, in the imperative, describing user-visible effect rather than
  implementation. "Retry failed webhooks" not "add retry loop to WebhookSender".
- Put anything touching `migrations/` or a public API under a **Breaking**
  heading at the top, even if the commit did not say so.
- Drop pure chores. If everything is a chore, say "no user-visible changes".

/incident

---
name: incident
description: Opens an incident investigation from a service name and a time window. Use when triaging a production alert, a pager, or a customer-reported outage.
allowed-tools: Bash(kubectl logs *) Bash(kubectl get *) Read Grep
---

Deployments in the last day:
!`kubectl get deploy -o wide --sort-by=.metadata.creationTimestamp`

Work the incident in this order and stop at the first one that explains it:

1. **Did something change?** Correlate the alert time against the deployment
   list above and against `git log --since` for the owning service.
2. **Is it saturation?** Check the pod restarts and the resource limits before
   reading a single line of application code.
3. **Is it a dependency?** Look for the error at the boundary before looking for
   it in our code.

Report: the one-line cause, the evidence, and the smallest safe mitigation.
Do not propose a permanent fix in the same message.

/deploy, deliberately manual

---
name: deploy
description: Deploys the named service to staging.
disable-model-invocation: true
allowed-tools: Bash(make deploy *) Bash(git status *)
---

Working tree:  !`git status --short`

Refuse to continue if the working tree is dirty; say what is uncommitted and
stop.

Otherwise run `make deploy ENV=staging SVC=<service>` and report the rollout
status. Never deploy to production from this skill.

The disable-model-invocation: true line is the whole point. Without it, Claude can decide on its own that a deploy is the reasonable next step.

/schema-versioning, reference only

---
name: schema-versioning
description: How event schema versioning works in this repository, including the compatibility rules and the deprecation window.
user-invocable: false
---

Events are versioned in the subject name, never in the payload...

No slash command is created. Claude pulls it in when a prompt touches event schemas, and it costs one description line the rest of the time.

Where skills live

Location Available in Invoked as
~/.claude/skills/<name>/SKILL.md All your projects /<name>
.claude/skills/<name>/SKILL.md This project, committed /<name>
packages/web/.claude/skills/<name>/ Loads when Claude touches packages/web/ /packages/web:<name> if the name collides
Plugin skills/<name>/SKILL.md Wherever the plugin is enabled /<plugin>:<name>

Supporting files sit alongside SKILL.md in the same directory and can be referenced by relative path: scripts, templates, checklists, reference docs.

Name collisions favour your personal skill. A ~/.claude/skills/deploy/ you wrote for a side project shadows a project’s committed .claude/skills/deploy/, silently. This is the opposite direction to agents. See skills vs agents.

Live reload

Edits to an existing SKILL.md are picked up immediately, no restart. The one exception: if you just created the top-level skills/ directory for the first time, restart once so it gets watched.