How to Create Claude Code Skills (SKILL.md)
A skill's description is cut at 1,536 characters in the skill listing. Where SKILL.md goes, which frontmatter keys exist, and how to write and invoke a skill.
- A skill is a runbook whose body loads only when invoked, unlike CLAUDE.md, which is read every session.
- Custom slash commands are merged into skills: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy; use the directory form for new work.
- When names collide, enterprise beats personal and personal beats project, and a skill beats a .claude/commands/ file with the same name.
A skill is a runbook that Claude loads only when it needs it. CLAUDE.md is read at the start of every session; a skill’s body is read only when the skill is invoked. This guide covers where skills live, a minimal SKILL.md, how to write the description, how to invoke a skill, and when to use CLAUDE.md, skills or hooks instead. In our test environment (macOS, Claude Code 2.1.304, on 2026-10-20), we put a skill into a dummy todo app and validated its format. We did not run a session that calls the model.
What a skill is
The docs suggest creating a skill when you keep pasting the same instructions, checklist or multi-step procedure into chat, or when part of CLAUDE.md has turned from a fact into a procedure. A skill’s body loads only when it’s used, so long reference material costs almost nothing until then.
Custom slash commands have been merged into skills. .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy and work the same way. Existing .claude/commands/ files keep working. For new work, use the directory form, which can also hold supporting files.
Source: https://code.claude.com/docs/en/skills (checked 2026-10-20)
Where skills live
| Location | Path | Loads in |
|---|---|---|
| Personal | ~/.claude/skills/<name>/SKILL.md | All your projects on this machine |
| Project | .claude/skills/<name>/SKILL.md | Sessions in this repository; commit it to share with your team |
| Plugin | <plugin>/skills/<name>/SKILL.md | Wherever the plugin is enabled, as /plugin-name:skill-name |
| Enterprise | .claude/skills/ in the managed settings directory | Every user on machines your organization deploys it to |
When two skills share a name, enterprise wins over personal, and personal wins over project. With deploy in both ~/.claude/skills/ and the project, /deploy runs the personal one. If a skill and a .claude/commands/ file share a name, the skill wins.
Source: https://code.claude.com/docs/en/skills (checked 2026-10-20)
A minimal SKILL.md
Here is a release runbook for a dummy todo app, saved as .claude/skills/release/SKILL.md:
---
name: release
description: Cut a release of the todo app. Use when the user asks to release, tag a version, or publish the changelog. Do not use for hotfix deploys or database migrations.
argument-hint: "[version]"
disable-model-invocation: true
allowed-tools: Bash(npm test) Bash(npm run build) Bash(git tag *)
---
Release version $ARGUMENTS of the todo app.
1. Run `npm test`. Stop and report if any test fails.
2. Run `npm run build`.
3. Add a `## $ARGUMENTS` section to CHANGELOG.md from the commits since the last tag.
4. Create the tag with `git tag v$ARGUMENTS`. Do not push.
5. Report the tag name and the changelog section.
The main frontmatter keys are below. None is required; the docs recommend at least description.
| Key | What it does |
|---|---|
name | Name shown in the / menu. Defaults to the directory name |
description | What the skill does and when to use it. Claude reads it to decide whether to load the skill |
when_to_use | Extra trigger context, appended to description |
argument-hint | Hint shown during autocomplete, such as [version] |
arguments | Named arguments for $name placeholders |
disable-model-invocation | true stops Claude from loading the skill on its own; you run it with /name |
user-invocable | false hides it from the / menu so only Claude uses it |
allowed-tools | Tools Claude can use without asking during the turn that invokes the skill |
disallowed-tools | Tools removed while the skill is active |
Source: https://code.claude.com/docs/en/skills (checked 2026-10-20)
Set disable-model-invocation: true on anything with side effects, like a release, so Claude doesn’t start it unprompted. allowed-tools does not restrict which tools exist. It only skips the permission prompt for that turn, and the grant clears when you send your next message.
Validate the format
claude plugin validate checks a skills directory without starting a session (v2.1.233 or later). The example above passed:
$ claude plugin validate .claude/skills
Validating components in: <project>/.claude/skills
✔ Validation passed
We added a skill with an unclosed quote in its frontmatter:
✘ Found 1 error:
❯ frontmatter: YAML frontmatter failed to parse: YAML Parse error: Unexpected character. At runtime this skill loads with empty metadata (all frontmatter fields silently dropped).
The validator did not catch a misspelled key. A skill with disable-model-invokation: true returned Validation passed, even with --strict. The docs say unknown keys are ignored without an error, so copy key names from the table.
Writing the description
Describe when to use the skill, not what it is. Claude reads the description to decide whether to load the skill, and the docs recommend including the words users would naturally say.
We follow two rules when we write one. First, name the cases where the skill should not be used. In the example, that’s “hotfix deploys or database migrations”. Second, describe when each tool is used, not just which tools exist.
In the skill listing, description and when_to_use together are cut at 1,536 characters, so put the main use first. For trimming what loads into every session, see Trim Your CLAUDE.md.
Source: https://code.claude.com/docs/en/skills (checked 2026-10-20)
Invoking a skill
By default, both you and Claude can invoke a skill.
- Automatically: when the description matches the conversation, Claude loads the body
- Manually: start your message with
/release 1.4.0. Writing/releasemid-sentence doesn’t run it directly; it gives Claude permission to use it in that message - Arguments:
$ARGUMENTSreceives everything after the name, and$0,$1pick arguments by position. With/release 1.4.0,$ARGUMENTSis1.4.0
With disable-model-invocation: true, the description stays out of context, and the body loads only when you invoke it. If Claude doesn’t use a skill, the docs suggest asking What skills are available? to check that it’s listed.
Source: https://code.claude.com/docs/en/skills (checked 2026-10-20)
We did not test automatic loading or /release in a live session (not run).
CLAUDE.md, skills or hooks
| Content | Where | Why |
|---|---|---|
| Facts needed every session (build commands, what each directory is for) | CLAUDE.md | Always read at session start |
| Multi-step procedures (releases, migrations) | Skill | The body loads only when used |
| Rules that must always hold (never read secret files) | Hooks | Run every time, whether or not Claude follows the skill |
The docs also say that when Claude stops following a rule from a skill partway through, a rule that must hold every time belongs in a hook. See Claude Code Hooks in Practice.
Summary
- A skill is a runbook whose body loads only when used. Custom slash commands are now skills.
- Skills live in personal, project, plugin or enterprise locations. For the same name, enterprise beats personal, and personal beats project.
- Give anything with side effects
disable-model-invocation: trueand run it with/name. - In the description, say when to use the skill and name the cases where it shouldn’t be used. Put the main use first; the listing cuts at 1,536 characters.
claude plugin validatecatches broken YAML but not misspelled keys.- Facts go in CLAUDE.md, procedures in skills, and must-hold rules in hooks.