mdskills
guides

How to Create a Claude Code Skill: A Complete SKILL.md Guide

Billboard advertising "skills the open agent skills ecosystem"
Photo by Igor Shalyminov on Unsplash

Most people use Claude Code like a chat window: type what you need, get a response, repeat. Skills are the step up from that. You teach Claude a task once, and it applies that knowledge automatically whenever the task shows up.

A skill is a directory with a SKILL.md inside. Claude loads it only when the situation matches, either because you typed /skill-name or because it decided the conversation calls for it. By the end of this guide you'll have a working skill on disk and know which frontmatter field decides whether Claude ever loads it on its own. That field is optional. Skip it and the skill runs when you type its name but never fires on its own, with no error to tell you why.

What you need before you start

Claude Code v2.1.145 or later ships with three bundled skills: /run, /verify, and /run-skill-generator. If you want nested skills inside a monorepo (a .claude/skills/ inside packages/frontend/, for instance), you need v2.1.203 or later. Check with claude --version or type /status in a session.

Personal skills live at ~/.claude/skills/<skill-name>/. They follow you across every project on the machine. Project skills go at .claude/skills/<skill-name>/, one repo only, travel with the repo in git. That second choice matters more than it looks. Personal skills are not loaded in Cowork or cloud sessions, so if a routine needs to invoke your skill, put it in the repo.

Create the skill directory and SKILL.md file

One directory per skill, named after the skill. That directory name is the slash command. Only one file is required inside it: SKILL.md, with YAML frontmatter on top and markdown instructions below. Anything else you drop in the directory (templates, example outputs, scripts, reference docs) is optional. Reference those files from the body so Claude knows what they contain and when to load them.

The summarize-changes skill below pulls the current git diff into context and flags anything risky in it. Create the directory first:

mkdir -p .claude/skills/summarize-changes

Then write .claude/skills/summarize-changes/SKILL.md:

---
name: summarize-changes
description: Summarize uncommitted changes in the current git repo and flag risky diffs. Use when the user asks what changed, what's uncommitted, or wants a review of their working tree.
when_to_use: User asks "what did I change", "summarize my diff", "review my uncommitted work", or similar.
argument-hint: "[optional-path]"
---

Read the uncommitted diff for the repository (or for the path argument if given).

Produce:
1. A one-paragraph summary of the intent of the changes.
2. A bulleted list of files touched, grouped by directory.
3. A "Risks" section flagging anything that looks like a secret, a deleted test, a lockfile change without a matching manifest change, or a schema migration.

Do not run git commit. Do not stage files.

Type /summarize-changes and it runs. Skip the description field and the slash command still works, but Claude will never load it on its own.

The full frontmatter field list from the docs:

FieldPurpose
nameDisplay name in listings. Defaults to the directory name.
descriptionWhat the skill does and when to use it. Claude's primary signal for auto-invocation. Recommended.
when_to_useExtra trigger phrases or example requests. Appended to description in the skill listing.
argument-hintText shown during autocomplete, like [issue-number] or [filename] [format].
argumentsNamed positional arguments for $name substitution in the body. Space-separated string or YAML list.
disable-model-invocationtrue to prevent Claude from loading the skill automatically. Default false.
user-invocablefalse to hide from the / menu while still letting Claude load it. Default true.
allowed-toolsTools Claude can use without asking permission during the invoking turn.
disallowed-toolsTools removed from Claude's pool while the skill is active.

Only description is recommended. The combined length of description plus when_to_use is truncated at 1,536 characters in the skill listing, so put the key use case first.

Keep the body short. Once a skill loads, its content stays in context across turns, so every line is a recurring token cost until the session ends. Say what to do and stop.

Pick where to store it

Skills load from four places:

  • Enterprise, via managed settings, applies to every user in your organization.
  • Personal at ~/.claude/skills/<skill-name>/SKILL.md, applies to every project on your machine.
  • Project at .claude/skills/<skill-name>/SKILL.md, applies to that repo only.
  • Plugin at <plugin>/skills/<skill-name>/SKILL.md, applies wherever the plugin is enabled.

Name collisions resolve top-down: enterprise beats personal, personal beats project, and any of them override a bundled skill with the same name. That last rule cuts both ways. Drop a code-review skill in .claude/skills/ and it silently replaces the bundled /code-review, no warning printed. Plugin skills dodge this by living in a plugin-name:skill-name namespace.

Monorepos got their own rule in v2.1.203. Claude Code loads project skills from .claude/skills/ in your starting directory and every parent up to the repo root, and additionally discovers nested .claude/skills/ directories on demand whenever Claude touches files inside them. Say a deploy skill exists at both the repo root and apps/web/.claude/skills/. The nested one shows up under the qualified name apps/web:deploy, and Claude picks the variant matching whichever files it's currently touching. /apps/web:deploy runs the nested one explicitly.

Symlinks work at the enterprise, personal, and project levels. Point ~/.claude/skills/summarize-changes at a shared directory elsewhere on disk and Claude Code follows the link. If two symlinks point at the same target, Claude still loads the skill once.

The caveat that trips people up. Personal skills in ~/.claude/skills/ do not load in Cowork or cloud sessions. Every routine run is a fresh remote session, and a personal-only skill returns "not found" every time. You have a few ways out. Enable the skill on your claude.ai account (Customize in the Desktop sidebar, or the skills settings on claude.ai). Commit it to the repo's .claude/skills/. Or ship it in a plugin declared in the repo's .claude/settings.json.

Control when Claude invokes the skill automatically

Every skill can be invoked two ways: you type /skill-name, or Claude decides on its own to load it based on what you're asking. That second path is the whole reason skills are worth setting up, and the frontmatter controls it end to end.

description is the primary signal Claude uses to decide. Claude reads the skill listing (every skill's description plus when_to_use, truncated at 1,536 characters combined) and decides whether the current conversation matches. Name the trigger explicitly in description. "Summarize uncommitted changes in the current git repo" is a signal. "Helps with development tasks" is not.

when_to_use holds your trigger phrases and example requests. It gets appended to description in the listing and counts toward the same 1,536-character cap. Treat it as a list of the actual sentences your users type.

Two frontmatter fields let you shut auto-invocation off:

  • disable-model-invocation: true means Claude cannot load the skill on its own. It only runs when you type /skill-name. Use this for destructive workflows (deploys, migrations, anything you never want triggered by an ambiguous prompt). As of v2.1.196, this also prevents the skill from running when a scheduled task fires with the skill as its prompt.
  • user-invocable: false hides the skill from the / menu but lets Claude still load it as background knowledge. Use it for reference material you want Claude to consult but users shouldn't call directly.

The docs also list context: fork as an option for running the skill in its own subagent context, separate from the main conversation.

One last thing about the body: since the skill stays in context across turns once loaded, verbose reference material inside SKILL.md costs tokens every turn until the conversation ends. Push long reference material into supporting files and mention them in the body so Claude loads them only when it needs to.

The silent failure: why your skill never fires automatically

You create a skill, you can type /skill-name and it runs, and you assume Claude will also load it on its own when the situation matches. It doesn't. Nothing errors. The skill just never fires unless you invoke it by hand.

The cause is almost always the description field, or the absence of one. When you omit description, Claude falls back to the first paragraph of the markdown body as the skill's listing entry. If that first paragraph starts with "This skill helps you..." or launches into implementation notes, the listing entry tells Claude nothing about when to use the skill, so Claude doesn't.

The 1,536-character truncation makes this worse. description and when_to_use are concatenated and cut off at 1,536 characters in the skill listing. Bury your key trigger phrase in the last sentence of a long when_to_use and it may get trimmed away. Front-load the use case.

The same-name shadowing rule causes another quiet failure. Create a project skill named code-review and it silently replaces the bundled /code-review. If you didn't intend to override it, you now have a worse /code-review than the one you started with, and no warning was printed.

Live change detection has its own edge. Claude Code watches skill directories and picks up added, edited, or removed skills mid-session. But creating a top-level skills directory that didn't exist when the session started (a fresh .claude/skills/ in a repo that had none) requires restarting Claude Code so the new directory can be watched. Edit inside an existing directory, no restart. Create a new top-level one, restart.

Verify the skill loads and invokes correctly

Type / in the Claude Code chat and start typing your skill name. It should autocomplete. If it doesn't, either the directory is in the wrong place, or you created a new top-level skills directory mid-session and need to restart. Check with /status that you're on a version recent enough (v2.1.145+ for /run and /verify, v2.1.203+ for nested skills).

If you set argument-hint, the hint should appear in the autocomplete UI once the name is selected. Run /skill-name and confirm the body instructions execute against your working directory.

For automatic invocation, phrase a request that matches your when_to_use. For the summarize-changes example above, try "what did I change" without typing the slash command. Claude should load the skill and produce the summary. If it doesn't, your description is not specific enough, or it's being truncated past the 1,536-character cap, or another skill's description is closer to what you typed and won the match.

Where to take your skill next

Once one skill works, the rest of the ecosystem opens up. Run /run-skill-generator once per project to record how your app installs, configures, and launches from a clean environment. It captures the install commands, env vars, and launch script and commits the recipe as a per-project skill at .claude/skills/run-<name>/. After that, /run and /verify follow your recipe instead of guessing from your package.json or Makefile. Run it again if the build changes.

Add supporting files to keep the body short: templates Claude fills in, example outputs showing the format you want, executable scripts Claude can run, reference docs it loads on demand. Reference them from SKILL.md so Claude knows what each file contains.

To share a skill across projects without duplicating it, symlink ~/.claude/skills/<name> to a shared directory elsewhere on disk. Claude Code follows the symlink and loads the skill once even if it's reachable from multiple paths.

Overriding a bundled skill is a same-name project skill: .claude/skills/code-review/SKILL.md replaces /code-review for that repo. And because Claude Code skills follow the Agent Skills open standard, the same directory works with other tools that implement it.

Frequently asked questions

What is a SKILL.md file and what goes inside it?

SKILL.md is the required entrypoint for every skill directory. It contains optional YAML frontmatter between --- markers (fields: name, description, when_to_use, argument-hint, arguments, disable-model-invocation, user-invocable, allowed-tools, disallowed-tools) followed by markdown body instructions. Only description is recommended. Everything else is optional.

What is the difference between a skill and a CLAUDE.md instruction?

CLAUDE.md content loads into context for every session, so it costs tokens on every turn whether you use it or not. A skill's body loads only when the skill is invoked, so long reference material or procedures in a skill cost almost nothing until you need them.

How do I invoke a skill directly in Claude Code?

Type /skill-name in the chat. The skill name is derived from the directory name. For nested monorepo skills, use the directory-qualified form like /apps/web:deploy to target a specific subdirectory variant.

Where should I store a skill to make it available across all my projects?

~/.claude/skills/<skill-name>/SKILL.md. That's the personal level, which applies to every local project. One caveat: personal skills are not loaded in Cowork or cloud sessions, so if you use those, enable the skill for your claude.ai account separately or commit it to the repo.

What happens when two skills share the same name at different levels?

Enterprise overrides personal, personal overrides project, and any of these override a bundled skill with the same name. Plugin skills use a plugin-name:skill-name namespace, so they cannot conflict with the other levels.

How do bundled skills like /code-review differ from built-in commands?

Bundled skills are prompt-based: they hand Claude detailed instructions and let Claude orchestrate the work with its tools. Built-in commands execute fixed logic directly. Both are typed as /name, but bundled skills can be overridden by a same-name skill or disabled with the disableBundledSkills setting. Built-in commands cannot.

How do I set up skills for a monorepo with multiple packages?

Put a .claude/skills/ directory inside each package, for example packages/frontend/.claude/skills/. Claude Code discovers these on demand when Claude works with files in that subdirectory. If a nested skill shares a name with a root skill, the nested one appears under a directory-qualified name like packages/frontend:deploy and Claude picks the variant matching the files it's touching. Requires v2.1.203 or later.

How do I use /run-skill-generator to teach Claude how to launch my app?

Run /run-skill-generator once per project (v2.1.145 or later). It gets your app running from a clean environment, captures the install commands, env vars, and launch script that worked, and commits the recipe as a per-project skill at .claude/skills/run-<name>/. After that, /run and /verify follow that recipe instead of inferring the launch from your project files. Run it again when the build or launch process changes.

<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "HowTo", "headline": "How to Create a Claude Code Skill: A Complete SKILL.md Guide", "description": "Create a working Claude Code skill from scratch. Full SKILL.md field reference, real syntax examples, storage locations, and the gotcha that breaks most first attempts." } </script> <!-- rankwright-verify: 18 unverified numeric claims flagged (threshold=20) -->
how to create a claude code skill

Related Articles