Claude Code skills not triggering? Four checks, in order
A skill that never fires is rarely a prompting problem. The four mechanisms that decide invocation, including the budget that silently strips descriptions.

You wrote a skill. It does exactly what you want when you type /your-skill. But Claude never reaches for it on its own, no matter how obviously relevant the moment is. The usual next move is to rewrite the instructions, add emphasis, maybe put CRITICAL in front of a sentence. That almost never helps, because the body of the skill is not what invocation is decided on.
We run a fleet of skills behind recal's autonomous growth loop, and the ones that failed were never the ones with weak instructions. They were the ones where something upstream of the instructions was broken, and the symptom looked identical in every case: works on the slash command, silent otherwise.
Here are the four mechanisms that decide whether a skill fires, in the order worth checking them, with the diagnostic for each.
Key takeaways
- Claude Code loads a listing of skill names and descriptions into context. The full body loads only once the skill is invoked, so the
descriptionis the only text invocation is matched against. - Malformed YAML frontmatter loads the body with empty metadata. The slash command keeps working and automatic invocation becomes impossible, which is exactly the symptom people misread as a prompting problem.
- The listing has a character budget that scales at 1% of the model's context window. When it overflows, descriptions are dropped starting with the skills you invoke least.
disable-model-invocation: truekeeps the description out of context entirely. It is the correct setting for/deployand the wrong setting for anything you want Claude to notice.- A same-named skill at a higher precedence level silently replaces yours. Enterprise beats personal, personal beats project, and any of them beats a bundled skill.
How does Claude decide which skill to use?
Claude Code does not read your skills at the start of a session. It loads a listing: every skill's name, plus its description. The body of SKILL.md stays on disk until the skill is actually invoked. That is the whole point of the design, since it means a long reference document costs almost nothing until the moment you need it.
The consequence is the part people miss. If invocation is matched against the listing, then every word of careful instruction inside the skill body is invisible at the moment the decision gets made. The description field is the entire surface area. Everything below is a way of asking whether your description made it into that listing intact.
Claude Code's own skills documentation is explicit that the description is "what the skill does and when to use it. Claude uses this to decide when to apply the skill." If the field is omitted, the first paragraph of markdown content is used instead, which is usually a worse match than something written on purpose.

Check 1: is the frontmatter actually parsing?
This is first because it is the most common and the most misleading.
If the YAML frontmatter in SKILL.md is malformed, Claude Code does not error out and does not skip the skill. It loads the skill body with empty metadata. The skill still appears, /skill-name still runs it, and everything looks fine from the outside. But there is no description for Claude to match against, so automatic invocation cannot happen at all.
That is why the "works manually, never fires automatically" symptom is so consistently misdiagnosed. It is not a soft matching failure that better wording will fix. It is a hard absence.
The diagnostic:
claude --debugRun with --debug and the YAML parse error is printed. If you see one, the wording of your description was never the problem.
The usual culprits are ordinary YAML potholes. An unquoted colon inside a description is the one that catches most people:
---
# breaks: the colon starts a mapping
description: Deploy checklist: run tests, then push
------
# fine
description: "Deploy checklist: run tests, then push"
---A second, quieter failure mode lives next door. Some validation paths outside Claude Code accept only the six fields in the Agent Skills spec (allowed-tools, compatibility, description, license, metadata, name) and reject anything else with an unexpected-key error. Claude Code accepts all six, so a spec-compliant file loads everywhere, but a file using Claude Code extensions will not necessarily survive a round trip through the API or claude.ai.
Check 2: did your description get truncated out of the listing?
This is the mechanism almost nobody knows about, and it gets worse the more skills you accumulate.
The skill listing has a character budget, and that budget scales at 1% of the model's context window. Every skill name is always included. Descriptions are not. When the listing overflows its budget, Claude Code starts dropping descriptions to fit.
Which descriptions go first is the interesting part: it drops them starting with the skills you invoke least, so the skills you use most keep their full text.
Sit with that for a second, because it forms a loop. A skill that has never fired is by definition a skill you have rarely invoked. That makes it a prime candidate to lose its description. Losing its description makes it impossible for Claude to match, which means it fires even less. A new skill added to a crowded listing can therefore be starved from the moment it lands, and no amount of rewriting the body will change it.
There is a second, independent cap underneath the budget. Each entry's combined description and when_to_use text is truncated at 1,536 characters in the listing, regardless of how much budget is left. If you wrote a long description with the trigger conditions at the bottom, the trigger conditions are what gets cut. Put the key use case first.
The diagnostics:
/doctor # estimates the listing's context cost and its biggest contributors
/context # the Skills row reports the listing size after the budget is applied
claude --debug # writes a warning when the listing exceeds its budgetOne caveat on /context: before v2.1.196, the Skills row counted the full text of every description and could report a number several times larger than the configured budget. On current versions it reflects what the model actually receives.
If you are genuinely over budget, you have three levers rather than one. Raise the budget with the skillListingBudgetFraction setting (0.02 for 2%) or set SLASH_COMMAND_TOOL_CHAR_BUDGET to a fixed character count. Free up space by setting low-priority entries to "name-only" in skillOverrides, which lists them without a description. Or trim the descriptions at the source, which is usually the right answer, since a description that needs 1,500 characters is describing too many things.
Check 3: is the skill allowed to be model-invoked?
Two frontmatter fields control who can invoke a skill, and one of them removes the description from context as a side effect.
| Frontmatter | You can invoke | Claude can invoke | Description in context |
|---|---|---|---|
| (default) | Yes | Yes | Yes |
disable-model-invocation: true | Yes | No | No |
user-invocable: false | No | Yes | Yes |
disable-model-invocation: true is a deliberate and often correct setting. It is what you want on /deploy, /commit, or anything with side effects whose timing you want to own. You do not want Claude deciding to ship because the code looks ready. Claude Code will block the call if the model tries anyway, and instruct it not to reproduce the steps some other way, so expect a suggestion that you run the command yourself.
It is also, occasionally, something that got pasted into a template and copied across a dozen skills that were never meant to be manual. Worth grepping for.
user-invocable: false is the mirror image, for background knowledge that Claude should have when relevant but that is not a meaningful action for a human to run. A skill explaining how a legacy system works fits; /legacy-system-context is not a command anyone wants to type.
Check 4: is something else shadowing your skill?
Skills live at four levels, and same-named skills override each other in a fixed order.
| Location | Path | Applies to |
|---|---|---|
| Enterprise | Managed settings | Everyone in the organization |
| Personal | ~/.claude/skills/<name>/SKILL.md | All your projects |
| Project | .claude/skills/<name>/SKILL.md | That project only |
| Plugin | <plugin>/skills/<name>/SKILL.md | Where the plugin is enabled |
Enterprise overrides personal, personal overrides project, and a skill at any level overrides a bundled skill of the same name. A code-review skill in your project replaces the bundled /code-review entirely. Plugin skills are namespaced as plugin-name:skill-name, so they cannot collide with the other levels.
The failure this produces is subtle: your project skill is running, but it is not your project skill. An old personal skill with the same directory name is winning, and it has a different description, so it matches on different requests than the one you just wrote.
The quick check is to ask Claude directly:
What skills are available?If your skill is missing from the answer, it is a discovery or precedence problem rather than a matching problem. If it is present but with a description you do not recognize, you have found your shadow.
One related trap, since custom commands were merged into skills: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both produce /deploy. Older command files keep working, which is convenient right up until you forget one exists.
What to actually write in a description
Once the four mechanisms above are clear, the writing advice is short and follows from them.
Write the trigger, not the capability. The description is matched against what a user says, so it should contain the words a user would say. "Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff" carries three concrete triggers. "Git helper utilities" carries none.
Put the key use case in the first sentence, because that is the text most likely to survive both the 1,536 character cap and the listing budget.
Use when_to_use for trigger phrases and example requests. It is appended to description in the listing and counts toward the same cap, so it is a place to put extra matching surface, not extra prose.
And if a skill genuinely fires too often, the fix is the same field in reverse: make the description more specific, or set disable-model-invocation: true and drive it manually.
The general shape here is one we keep relearning while building an agent that has to invoke its own capabilities unattended. A rule with no trigger is not a rule. An instruction that says "remember to check the notifications page" will never fire, and the same instruction written as a named item in a checklist the agent already walks fires every time. Skills work the same way, one level up: the body is where the work is described, and the description is the only thing deciding whether the work ever starts.
FAQ
Why does my skill work with the slash command but never trigger automatically?
Almost always malformed YAML frontmatter. Claude Code loads the body with empty metadata when the frontmatter fails to parse, so the command still resolves but there is no description to match against. Run claude --debug to see the parse error. The second most likely cause is disable-model-invocation: true, which blocks model invocation by design.
Is there a character limit on a Claude skill description?
Yes, two of them. The combined description and when_to_use text is truncated at 1,536 characters per entry in the listing (configurable with skillListingMaxDescChars). Separately, the whole listing has a budget scaling at 1% of the model's context window, and descriptions are dropped when it overflows.
Why did my skill stop triggering after I added more skills?
Most likely the listing budget. When the listing overflows, Claude Code drops descriptions starting with the least-invoked skills, which is disproportionately likely to be the newest ones. Run /doctor to see the listing's cost and its biggest contributors.
How do I check what Claude can actually see?
Ask "What skills are available?" in a session, run /doctor for the listing's context cost, and check the Skills row in /context for the post-budget size. For parse errors and budget warnings, run with --debug.
Does the name field control the command I type?
Not for personal or project skills. There, name sets only the display label in listings, and the command comes from the directory or file name. In a plugin skill, name sets the last segment of the command and the plugin prefix stays in place.
Methodology and sources
Every mechanism described here is documented behavior, checked against Claude Code's skills documentation and the Agent Skills open standard on 2026-08-08, not inferred from observed behavior. Version-specific notes (/context reporting before v2.1.196) are called out where they apply, since the numbers shift between releases. The framing and the ordering of the checks come from running a skill fleet in an unattended loop, where a skill that fails to fire produces no error and no output, only silence.
This post was researched and drafted with AI assistance, then verified against primary documentation and edited by the recal team.