CCLearn

Frontmatter that matters: description, invocation, tools

verified against Claude Code 2.1.261 · 2026-09-0916 min

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 description is 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 description and when_to_use text 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.

yaml
---
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.

FrontmatterYou can invokeClaude can invokeWhen loaded into context
(default)YesYesDescription always in context, full skill loads when invoked
disable-model-invocation: trueYesNoDescription not in context, full skill loads when you invoke
user-invocable: falseNoYesDescription 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-tools field 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 fieldWhat it isWhat it is not
allowed-toolsPre-approval, so the turn runs without permission promptsA restriction or a durable allowlist
Its lifetimeThe invoking turn; re-invoking re-applies itAnything surviving your next message
Everything unlistedGoverned by your normal permission settingsBlocked
yaml
---
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.

  • model applies 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, plus inherit. A value excluded by your organisation's availableModels allowlist is not used, and the session keeps its current model.
  • effort overrides the session effort level while the skill is active, across low, medium, high, xhigh and max, with availability depending on the model.
  • paths takes 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 what paths does when you type /name yourself, 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: fork to 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

  1. You have a /release skill 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 answer

    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: false instead. That hides /release from 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.

  2. 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 answer

    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-tools is 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 that Bash(npm test *) ran without a permission prompt. Reading the field as a sandbox gets it exactly backwards — disallowed-tools is the one that removes things, and it clears on the same boundary.

  3. Your api-conventions skill lists your team's REST rules and nothing else. A colleague adds context: fork so it "does not clutter the main context". What do you expect the next invocation to return, and why?

    Show answer

    Expect it to come back without meaningful output. With context: fork the 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: fork only 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.

  4. A teammate reports that /deploy works but Claude never picks the skill up on its own, and the description looks fine to you. Name two different frontmatter causes that each produce exactly this symptom.

    Show answer

    One: disable-model-invocation: true is 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 /deploy in 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 /deploy working, because the command comes from the directory name, while Claude has no description to match; --debug shows the parse error. That is why "the description looks fine to you" is the clue — it reads fine and was never read as frontmatter. (A paths glob that does not match the files in play also limits automatic activation, but the docs say nothing about what paths does when you type /name yourself, so it is not a clean third answer here.)

Sources