A close read of Claude Code Skills — reusable instructions whose descriptions stay lightweight, while their full content loads only when invoked.
CLAUDE.md enters the session context whether the current task needs it or not. A skill keeps its full instructions deferred until invocation.
A skill is a directory. SKILL.md is the only file it needs — frontmatter on top, instructions below, extra files alongside.
my-skill/ ├── SKILL.md # required — overview + nav ├── reference.md # read only when needed ├── examples.md # read only when needed └── scripts/ └── helper.py # usually executed for output
--- name: commit description: Stage and commit changes. Use when the user asks to commit. allowed-tools: Bash(git add *) --- # instructions, third person, # under 500 lines 1. Review the diff 2. Stage relevant files 3. Write a commit message
This is the whole point of a skill: content arrives in layers, and each layer only loads when the one before it justified it.
| Level | When | Cost | Content |
|---|---|---|---|
| 1 · Listing | At startup, when model-invocable | small, variable | description + optional when_to_use |
| 2 · Instructions | When the skill is invoked | full rendered body | The rendered SKILL.md content enters as one message |
| 3 · Resources | Only when Claude reads or runs them | deferred until access | References, examples, assets, and script output |
For model-invocable skills, Claude sees the listing text — description plus optional when_to_use — before it sees the body.
Says what, not when. Claude has no signal for which requests should trigger it.
Key use case first, then explicit trigger language — both the what and the when.
By default both paths are open at once — you can always type the name, and Claude can always decide the description matches.
Two booleans narrow the default "either of us can trigger this" down to exactly one side.
| Frontmatter | You invoke | Claude invokes | Use it for |
|---|---|---|---|
| (default) | yes | yes | Most skills — let either side reach for it |
| disable-model-invocation: true | yes | no | Side-effecting actions — /deploy, /commit. You decide the timing, not "the code looks ready." |
| user-invocable: false | no | yes | Background knowledge with nothing for a person to run — legacy-system-context |
Location defines scope. Name collisions are resolved across levels; plugins remain namespaced alongside local skills.
Every user in the org. Overrides everything else with the same name.
All of your projects. Overrides a project skill of the same name.
The project and parent directories to the repo root; nested variants appear on demand.
Namespaced plugin:skill — never conflicts, loads alongside.
allowed-tools and disallowed-tools both apply only for the turn that invokes the skill — then they clear.
--- name: commit disable-model-invocation: true allowed-tools: Bash(git add *) Bash(git commit *) ---
--- name: background-loop disallowed-tools: AskUserQuestion --- # keeps an autonomous skill from # ever stopping to ask a person
Rendered skill content remains in the conversation. Claude Code does not automatically re-read the file on each later turn.
A line starting with ! runs before Claude ever sees the skill — its output replaces the placeholder, the command itself never does.
--- description: Summarize uncommitted changes and flag risks. --- ## Current changes !`git diff HEAD` ## Instructions Summarize in 2-3 bullets, then list risks …
# the actual diff text, # already inlined — # not the git command diff --git a/app.py b/app.py + def validate(x): + return x is not None …
With context: fork, the skill body becomes the subagent's task. The parent conversation history is not passed into that isolated context.
A skill is a bet that most turns will not need its full procedure: expose a precise matching signal, load instructions only on invocation, and defer supporting resources until the task actually needs them.