Take .claude/commands/changed.md from the last module, make a directory called changed, and move the file into it as SKILL.md. You now have a skill. /changed still works, the body is unchanged, and nothing you learned about $0 or ! injection has been invalidated.
Four things arrive with the directory. Supporting files that sit next to SKILL.md and load only when Claude reads them. A scripts/ folder for code that gets executed rather than read into context. The name and paths frontmatter fields, which a command file ignores. And automatic invocation, where Claude decides the skill is relevant and loads it without you typing anything.
The docs recommend skills over command files for exactly the first reason: they support supporting files.
The shape on disk
.claude/skills/changed/scripts/summarize.sh ships to your plugin Executed, never loaded into context. Its length costs you nothing.
#!/usr/bin/env bash
set -euo pipefail
path="${1:-.}"
git diff --stat -- "$path" || true
git diff --name-only -- "$path" | head -50 .claude/skills/changed/SKILL.md ships to your plugin The command name comes from the directory around this file, not from anything inside it.
---
description: Summarize uncommitted changes and flag anything risky.
when_to_use: When the user asks what changed, wants a commit
message, or asks you to review their diff.
argument-hint: [path]
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/summarize.sh *)
---
Run `${CLAUDE_SKILL_DIR}/scripts/summarize.sh $0` and read
its output.
Summarize the changes in two or three bullets, then list
anything risky: missing error handling, hardcoded values,
tests that will now fail.
Conventions for this repo are in reference.md next to this
file. Read it when the diff touches the API layer. Read more
The allowed-tools rule and the body both reference ${CLAUDE_SKILL_DIR}. That pairing is what lets the bundled script run without a permission prompt: the grant names the exact resolved path, so the call matches an allow rule rather than falling through to ask.
.claude/skills/changed/reference.md ships to your plugin Loaded only if Claude reads it. Put the long tail here, not in SKILL.md.
# Repo conventions
API handlers return typed errors, never raw strings.
Migrations are forward-only.
Anything under legacy/ is exempt from all of the above. .claude/commands/changed.md The old file. Delete it after promoting.
---
description: Summarize uncommitted changes and flag anything risky
---
(superseded by .claude/skills/changed/) Read more
If you leave it, nothing breaks: when a skill and a command file share a name, the skill wins. But two files claiming one name is a thing you will misread in six months.
Where the name comes from
For a personal or project skill, the command name is the directory name. deploy-staging/SKILL.md gives you /deploy-staging. Frontmatter name is a display label in listings and nothing more.
That rule has one exception, and it is the reason people get confused. For a plugin skill, frontmatter name does set the last segment of the command, so name: fancy in a plugin called my-plugin produces /my-plugin:fancy. You will meet that in module 12. Until then, the directory decides.
| Layout | Name comes from |
|---|---|
.claude/skills/x/SKILL.md | directory name |
~/.claude/skills/x/SKILL.md | directory name |
.claude/commands/x.md | file name without extension |
<plugin>/skills/x/SKILL.md | frontmatter name or directory name, plugin-namespaced |
You want the skill to be called /diff instead of /changed, so you set name: diff in the frontmatter, save, and type /diff. Nothing happens. /changed still works.
The frontmatter name changed the label shown in listings and left the command alone. Rename the directory. That is the only lever for a personal or project skill: for skills you write outside a plugin, name never determines what you type.
description and when_to_use
description says what the skill does and when it applies. when_to_use carries the trigger phrases and is appended to the description. They are one budget: the combined text is truncated at 1,536 characters in the listing Claude sees. If you omit description entirely, the first paragraph of the body is used.
Write when_to_use in the vocabulary a user would actually type. “When the user asks what changed, wants a commit message, or asks you to review their diff” earns its place. “For diff-related tasks” does not.
Who can invoke what
Two true-or-false fields produce three useful states.
| Frontmatter | You invoke | Claude invokes | What sits in context |
|---|---|---|---|
| default | yes | yes | description always; full body on invoke |
disable-model-invocation: true | yes | no | nothing until you invoke |
user-invocable: false | no | yes | description always; full body on invoke |
The docs sort skills by what is in them. Reference content, such as conventions or a style guide, is worth letting Claude reach for on its own. Task content, such as deploy or send-slack-message, should be disable-model-invocation: true. The stated reason is blunt: “You don’t want Claude deciding to deploy because your code looks ready.”
disable-model-invocation: true also blocks preloading into subagents, and blocks scheduled tasks from firing the skill.
Tool grants clear on your next message
allowed-tools pre-approves tools for the invoking turn only. Your next message clears the grant. disallowed-tools behaves the same way in reverse, removing tools from the pool while the skill is active.
Both fields swap ${CLAUDE_SKILL_DIR} and ${CLAUDE_PROJECT_DIR} for the real folder they stand for, and so does the body. That double substitution is the mechanism that makes a bundled script runnable without a prompt:
---
name: render-chart
description: Render a chart from a CSV file
allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/render.sh *)
---
Run `${CLAUDE_SKILL_DIR}/scripts/render.sh <csv-file>` to render the chart.
The rule and the call resolve to the same absolute path, so the call matches an allow rule. Write the grant with a relative path and it will not match, and you will get an approval prompt on every run.
The economics of progressive disclosure
The description is always in context, for every skill Claude can see, in every session. It is a fixed tax you pay whether or not the skill is ever used. Listings have their own budget, governed by skillListingBudgetFraction, which defaults to 0.01 of the context window. Go over it and the descriptions of your least-used skills get dropped, which means they stop being matchable.
The body loads when the skill is invoked, and then stays for the whole session. Claude Code does not re-read the file on later turns.
One line always, the rest only on demand: that split is what progressive disclosure names in the heading above. Two consequences follow from it directly.
First, write standing instructions, not one-time steps. A body that reads “now do X, then report back” is wrong, because that text is still sitting in context four turns later saying “now do X”. Write rules that remain true.
Second, every line in the body is a recurring cost for the rest of the session, not a one-time read. A 400-line skill invoked in minute two is still occupying context in minute ninety. The docs cap the guidance at 500 lines, and that is a ceiling rather than a target. Anything that can live in a sibling reference.md and be read on demand should. Anything that can be a script in scripts/ costs nothing at all, because it is executed rather than loaded.
Re-invoking a skill whose rendered content has not changed adds only a short “already loaded” note.
A long session eventually runs out of room, and Claude Code compacts it to make space. Under that auto-compaction, the most recent invocation of each skill is re-attached, keeping the first 5,000 tokens of each within a shared 25,000-token budget, filled newest first. Older skills fall off the end.
Precedence
Module 3 said instruction files have no precedence, no fixed order that picks a winner. Skills do. When the same skill name exists at more than one level, the winner is fixed:
enterprise > personal > project.
Personal beats project. That is the opposite of what most configuration systems do, and the opposite of what you will find in module 8, where subagents resolve managed > project > user. Two surfaces, two orders. Do not generalise from this one.
The rest of the resolution rules, in order: any of the three levels overrides a bundled skill of the same name, but not that bundled skill’s aliases, so a project code-review skill does not capture /review. Plugin skills carry their plugin’s name in front, as plugin-name:skill-name, so two plugins can each ship a deploy and never collide. That prefix is a namespace. A skill beats a same-named .claude/commands/ file. Anything local beats a claude.ai-synced skill.
Project skills load from .claude/skills/ in your working directory and every parent up to the repo root. Skills nested below your working directory load lazily, meaning not until Claude reads or edits a file in that subtree, and on a name clash they take the directory as a prefix, such as /apps/web:deploy.
Promote your module 4 command. Create .claude/skills/changed/, move the file in as SKILL.md, and delete the old command file. Then earn the directory: move the shell out of the ! block into scripts/summarize.sh, add an allowed-tools rule naming it through ${CLAUDE_SKILL_DIR}, and put your repo’s conventions in a sibling reference.md that the body points at rather than inlines. Add when_to_use with phrases you would actually type. Run /changed, confirm no permission prompt appears, then count the lines in SKILL.md. This directory ships to skills/ in your plugin.
Further than anything else you will write in this course. A SKILL.md directory is read by every harness surveyed except Aider, and four of them read .claude/skills/ directly with no changes at all: Copilot CLI, Cline, Windsurf, and OpenCode. Copilot’s docs call the format “an open standard”. Windsurf gates it behind a setting for reading Claude Code config, and invokes with @skill-name rather than a slash.
The rest want the identical directory at a different path, and .agents/skills/ is the one converging on neutral ground. Codex CLI, Cursor, Copilot CLI, Amp, Zed, Windsurf, and OpenCode all read it. That is a convention observed across seven products’ documentation rather than a published spec with a URL, so treat it as a strong default and not a guarantee.
One caveat when you export outward. Outside Claude Code, on claude.ai uploads and the Skills API, only six frontmatter fields are legal: name, description, license, compatibility, metadata, allowed-tools. An argument-hint is a hard error there, not a warning.
Check yourself
- Your skill lives in
.claude/skills/ship/and its frontmatter saysname: deploy. What do you type? - A personal skill and a project skill are both called
changed. Which one runs? - You invoke a skill in your second message and keep working for an hour. How many times is
SKILL.mdread from disk?