Frontmatter that matters: description, invocation, tools
By the end you can
- Write a description and when_to_use that fire on the requests you mean
- Choose between disable-model-invocation and user-invocable, and say what each costs in context
- State exactly how long an allowed-tools grant lasts and what it does not do
- Use model and effort as overrides that expire, and paths as a filter on automatic activation
- Say what context fork changes about a skill and when it produces nothing useful
- Say what the name field controls, and what silently happens when frontmatter is misplaced or malformed
The skill you wrote in Lesson 7.1 works: you type /deploy and it runs. Two
things go wrong later. First, Claude decides your code looks ready and runs it
for you. Second, a different skill you wrote never fires at all, however you
phrase the request. Both are frontmatter problems, and neither announces itself
as one.
Every field is optional, and one is not
All fields are optional. Only
descriptionis recommended so Claude knows when to use the skill. — Skills
Three parsing facts first, because each of them fails silently. Frontmatter is
read only when the opening --- is the file's first line — otherwise Claude
Code treats the whole file, --- markers included, as skill content, and your
description is just body text. If the YAML is malformed, Claude Code loads the
skill body with empty metadata, so /skill-name still works but Claude has no
description to match against; --debug shows the parse error. And boolean
fields accept yes, no, on, off, 1 and 0 in any letter case as well as
true and false — a widening that arrived in v2.1.218, before which only
true and false parsed.
Description and when_to_use: the matching surface
description is not documentation. It is the text Claude matches your request
against when deciding whether to load the skill on its own — the only thing it
sees before the body exists in context.
What the skill does and when to use it. Claude uses this to decide when to apply the skill. If omitted, uses the first paragraph of markdown content. Put the key use case first: the combined
descriptionandwhen_to_usetext is truncated at 1,536 characters in the skill listing to reduce context usage. — Skills
Three consequences, in the order they bite. The cap covers the pair, and it
truncates rather than summarises — when_to_use holds trigger phrases or
example requests, is appended to description, and counts toward the same 1,536
characters. One budget, two fields, and anything past it is not there at all.
Omitting description does not mean no description: the first paragraph of
your markdown becomes one, so "This skill helps with our deployment process" is
then your whole matching surface. And write the words a person would type —
the documented first move when a skill will not trigger is to check the
description includes keywords users would naturally say, and the first move when
it triggers too often is to make the description more specific. Both are edits
to the same sentence.
---
name: migrate-component
description: Convert a React class component to a function component with hooks. Use when someone asks to modernise, convert, or rewrite a class component.
when_to_use: Trigger phrases include "convert this to hooks", "class to function component", "modernise this component".
---name does less than you expect
Lesson 7.1 established that the directory name becomes the command. A name
field does not change that: in a personal or project skill it
sets only the display label shown in skill listings,
and the command still comes from the directory name. So name: deploy-production
inside .claude/skills/deploy/SKILL.md changes what the listing says and leaves
/deploy exactly where it was. To change the command, move the directory. Only
in a plugin skill does name set the last segment of the command, and even
there the plugin prefix stays in place.
Who is allowed to invoke it
By default both of you can. Two fields narrow that, and they are not mirror images in the way the names suggest.
| Frontmatter | You can invoke | Claude can invoke | When loaded into context |
|---|---|---|---|
| (default) | Yes | Yes | Description always in context, full skill loads when invoked |
disable-model-invocation: true | Yes | No | Description not in context, full skill loads when you invoke |
user-invocable: false | No | Yes | Description always in context, full skill loads when invoked |
The third column is the part people miss. disable-model-invocation is not only
an access decision — it takes the description out of the listing entirely, since
Claude has no reason to carry a description for a skill it can never choose.
user-invocable: false saves nothing: Claude still needs to know the skill is
there.
The docs name the situation for each. disable-model-invocation: true is for
workflows with side effects or whose timing you want to control, like /commit,
/deploy, or /send-slack-message — because you don't want Claude deciding to
deploy because your code looks ready. user-invocable: false is for background
knowledge that isn't actionable as a command, such as a legacy-system-context
skill explaining how an old system works. The first also reaches past the slash
command: it prevents the skill being
preloaded into a subagent, since
preloading draws from the same set of skills Claude can invoke, and as of
v2.1.196 stops it running when a scheduled task fires with that skill as its
prompt. One field, three doors.
Tool grants that last exactly one turn
allowed-tools looks like a sandbox and is the reverse of one.
The
allowed-toolsfield grants permission for the listed tools during the turn that invokes the skill […] It does not restrict which tools are available: every tool remains callable, and your permission settings still govern tools that are not listed. — Skills
| The field | What it is | What it is not |
|---|---|---|
allowed-tools | Pre-approval, so the turn runs without permission prompts | A restriction or a durable allowlist |
| Its lifetime | The invoking turn; re-invoking re-applies it | Anything surviving your next message |
| Everything unlisted | Governed by your normal permission settings | Blocked |
---
name: commit
description: Stage and commit the current changes
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *) Bash(git status *)
---That pairing is the house pattern: a side-effecting skill only you can start,
granted exactly the commands it needs for exactly the turn it runs in. Lesson
4.5 argued for commit hygiene under an agent; this is that argument written as
configuration. disallowed-tools is the field that actually removes things —
tools taken out of Claude's available pool while the skill is active, for
autonomous skills that should never call a particular one — and it clears on the
same boundary, your next message. Pre-approving for a whole session, or blocking
something everywhere, means permission settings instead of either field. That is
Level 10.
model, effort and paths
These read like settings and are not. model and effort are overrides that
expire; paths narrows activation rather than access.
modelapplies for the rest of the current turn and is not saved to settings — the session model resumes on your next prompt. Same values as/model, plusinherit. A value excluded by your organisation'savailableModelsallowlist is not used, and the session keeps its current model.effortoverrides the session effort level while the skill is active, acrosslow,medium,high,xhighandmax, with availability depending on the model.pathstakes glob patterns that limit when the skill is activated: Claude loads it automatically only when working with files matching the patterns, using the same format as path-specific rules. Note automatically: what is documented is the automatic load. The docs say nothing about whatpathsdoes when you type/nameyourself, so use it to scope Claude's selection and do not treat it as a lock on the command.
context: fork, in one paragraph
Add
context: forkto your frontmatter when you want a skill to run in isolation. The skill content becomes the prompt that drives the subagent. It won't have access to your conversation history. — Skills
That last clause is the whole design decision. The body stops being guidance
layered onto your conversation and becomes a standalone task handed to a fresh
context — which is why the docs warn that context: fork only makes sense for
skills with explicit instructions. A skill of guidelines like "use these API
conventions" with no task gives the subagent the guidelines and no actionable
prompt, and it returns without meaningful output. Reference content stays
inline; task content is what forks. Two consequences to carry into Level 8. The
fork runs in the background by default, and background: false makes the
invoking turn wait for the result — a field that arrived in v2.1.218, before
which a forked skill always blocked the turn until it finished. And a
backgrounded fork applies its edits outside your session's checkpoints, so
/rewind does not undo them and git must. Lesson 3.4 taught rewind as the cheap
escape hatch — this is the case where it is not there.
The shape of the decision
Four questions, in this order, for every skill you write: what request should load this, who may start it, what must run without a prompt, and does the body need its own context. Everything else in the field reference refines one of those four.
Checkpoint
-
You have a
/releaseskill with side effects and want Claude to stop running it on its own. Which field, and what happens to that skill's description in the context listing once you set it?Show answerAnswer
disable-model-invocation: true. The description then leaves the listing entirely — it is not in context at all, because Claude has no reason to carry a description for a skill it can never choose. You keep/release, and the field also stops the skill being preloaded into a subagent and, from v2.1.196, stops it running when a scheduled task fires with it as the prompt.The trap is reaching for
user-invocable: falseinstead. That hides/releasefrom the/menu and from you while leaving Claude able to invoke it, and it saves nothing in context — Claude still needs to know the skill is there. -
A skill sets
allowed-tools: Bash(npm test *). You invoke it, Claude runs the tests, and you reply "now fix the failures". Does Claude still have that grant? Does it have any tool it lacked before you invoked the skill?Show answerAnswer
No to both. The grant covers the turn that invokes the skill, and your reply starts a new one, so the pre-approval is gone; re-invoking the skill re-applies it. Nothing survives your next message.
And Claude gained no tool it did not already have.
allowed-toolsis pre-approval, not access: "It does not restrict which tools are available: every tool remains callable, and your permission settings still govern tools that are not listed." The only difference during that turn was thatBash(npm test *)ran without a permission prompt. Reading the field as a sandbox gets it exactly backwards —disallowed-toolsis the one that removes things, and it clears on the same boundary. -
Your
api-conventionsskill lists your team's REST rules and nothing else. A colleague addscontext: forkso it "does not clutter the main context". What do you expect the next invocation to return, and why?Show answerAnswer
Expect it to come back without meaningful output. With
context: forkthe skill content stops being guidance layered onto your conversation and becomes the prompt that drives a subagent with no access to your conversation history — so a body of guidelines and no task hands that subagent rules to follow and nothing to do.The docs are explicit that
context: forkonly makes sense for skills with explicit instructions. The dividing line is content type, not context cost: reference content stays inline, task content is what forks. Your colleague also lost the thing they wanted, since the conventions now land in an isolated context instead of the conversation they were meant to steer. -
A teammate reports that
/deployworks but Claude never picks the skill up on its own, and thedescriptionlooks fine to you. Name two different frontmatter causes that each produce exactly this symptom.Show answerAnswer
One:
disable-model-invocation: trueis set. You can invoke, Claude cannot, and the description is not in the listing for it to match against — which is the documented purpose of the field on a/deployin the first place.Two: the frontmatter never parsed. Either the opening
---is not the file's first line, in which case the whole file including the---markers is treated as skill content, or the YAML is malformed, in which case the body loads with empty metadata. Both leave/deployworking, because the command comes from the directory name, while Claude has nodescriptionto match;--debugshows the parse error. That is why "the description looks fine to you" is the clue — it reads fine and was never read as frontmatter. (Apathsglob that does not match the files in play also limits automatic activation, but the docs say nothing about whatpathsdoes when you type/nameyourself, so it is not a clean third answer here.)