Skillforge Field notes on shipping with AI tools

Claude Code /skill-doctor: Find Unused Skills and Their Cost

claude-codeskillscosts

Every skill you install in Claude Code costs context on every turn, whether or not Claude ever uses it. /skill-doctor, announced in the 2.1.261 changelog on September 4, 2026, shows what each skill costs and how often it actually gets invoked, so you can turn off the ones that only take up room. This article covers what the report contains, why the cost exists in the first place, the one requirement that makes the command silently unavailable on some machines (including ours), and the three levers you pull once you have the numbers. Everything here was checked against the Claude Code docs on September 8, 2026, on Claude Code 2.1.263.

Why an unused skill costs anything at all

A skill is a folder with a SKILL.md file. Claude Code does not load the body of every skill into the conversation. It loads a listing: each skill's name and its description (plus when_to_use text, if present), so Claude knows what is available and can decide when to invoke one. The body loads only on invocation. That is the progressive-disclosure design covered in our skills tutorial, and it is what lets you run thirty skills without thirty documents in context.

The listing is not free, though. It is sent on every turn, and it grows with every skill you add. Claude Code caps it at a share of the context window, controlled by the skillListingBudgetFraction setting, whose default is 0.01: one percent of the window. When the listing overflows the cap, Claude Code keeps every skill's name but drops descriptions, starting with the skills you invoke least. A skill with no description in the listing can still be invoked by name, but Claude is much less likely to pick it on its own, because the keywords it would match against are gone.

There is a second cap per entry: each skill's combined description and when_to_use text is cut at 1,536 characters regardless of budget (skillListingMaxDescChars). Put the key use case first in a description, so the part that survives is the part that matters.

So the cost of an unused skill is twofold. It spends listing budget on every request, and once you have enough skills, it pushes other skills' descriptions out of the listing. That second effect is the sneaky one: you add a skill for one project, and a skill you use daily starts triggering less often, with no error anywhere.

How big is a listing in practice

We measured our own pack. ShipKit ships 25 skills, and their name plus description text totals 5,031 characters, an average of 201 per skill, the longest at 222. That is a listing built deliberately short (each description also has to pass the 200-character cap that the claude.ai upload path enforces, which we covered in the Cowork skills article). A pack of 25 skills with 1,000-character descriptions would be five times that before the budget starts cutting.

The script we used, if you want the same number for your own skills directory:

// node listing-size.js <dir>  (walks for SKILL.md files, sums name + description)
const fs = require('fs');
const path = require('path');
const root = process.argv[2] || '.claude/skills';
const files = [];
(function walk(d) {
  for (const e of fs.readdirSync(d, { withFileTypes: true })) {
    const p = path.join(d, e.name);
    if (e.isDirectory()) walk(p);
    else if (e.name === 'SKILL.md') files.push(p);
  }
})(root);
let total = 0;
for (const f of files) {
  const t = fs.readFileSync(f, 'utf8');
  const fm = (t.match(/^---\n([\s\S]*?)\n---/) || [, ''])[1];
  const name = (fm.match(/^name:\s*(.*)$/m) || [, path.basename(path.dirname(f))])[1];
  const desc = (fm.match(/^description:\s*(.*)$/m) || [, ''])[1];
  total += name.length + desc.length;
  console.log(String(name.length + desc.length).padStart(5), name);
}
console.log('skills', files.length, 'total chars', total);

It counts characters, not tokens, because the budget and the per-entry cap are both expressed in characters. The /skills menu shows token counts directly (press t to sort by them), which is the number to use when you want to compare against the /context breakdown.

What /skill-doctor reports

Run /skill-doctor in a session and Claude Code opens the report in the /plugin manager, on its Stats tab. Run it in non-interactive mode with claude -p "/skill-doctor" and it prints the report as text, which is the form to use if you want to save it or diff it later.

According to the docs, the report:

  • Shows what each skill in your session costs in context and how often it gets used.
  • Covers your skills other than bundled skills (the ones Claude Code ships, such as /doctor and /simplify) and enterprise skills.
  • Flags skills in the listing that have never been invoked and says where to turn them off.
  • Lists plugins you have not used recently.

The plugin part uses the same rule as the Not used recently header in the /plugin manager's Installed tab: a marketplace plugin you installed yourself and have not used in at least two weeks, over a span of at least ten sessions. Organization-managed plugins, plugins loaded with --plugin-dir, and plugins that only contribute a theme, output style, monitor, or workflow are never listed as unused, since they deliver value without an invocation to count.

We were not able to reproduce the report on our own machine, for the reason covered in the next section, so we are not going to describe its layout beyond what the docs state. When you run it, the two columns that matter are cost and invocations, and the interesting rows are the ones with a non-trivial cost and zero invocations.

The requirement that makes it "Unknown command"

The docs list two requirements. The command needs Claude Code v2.1.252 or later, and it needs feature-flag fetching. The second one is the trap.

Claude Code turns some features on through flags it fetches from Anthropic at startup. It skips that fetch in these sessions:

  • Any session where DISABLE_GROWTHBOOK, DISABLE_TELEMETRY, DO_NOT_TRACK, or CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC is set. Note that DISABLE_TELEMETRY and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC opt out when set to any non-empty value, including 0 or false.
  • Sessions on a third-party provider (Amazon Bedrock, Claude Platform on AWS, Google Cloud's Agent Platform, Microsoft Foundry), unless a host platform sets CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST.
  • Claude apps gateway sessions.

With fetching off, /skill-doctor is not registered at all. Here is what that looks like. Our ops machine runs Claude Code 2.1.263, well past both version numbers, and has DO_NOT_TRACK=1 in its environment (it comes from the organization's managed settings, not from anything in the repo):

$ claude -p "/skill-doctor"
Unknown command: /skill-doctor

Nothing in that message points at telemetry. The command that does is claude doctor, the read-only terminal diagnostic, which on the same machine prints this inside its Remote Control section:

- Feature-flag evaluation disabled (disabled by DO_NOT_TRACK)

So the checklist when /skill-doctor comes back unknown is: run claude --version and confirm 2.1.252 or later, then run claude doctor and look for a "Feature-flag evaluation disabled" line. If it names a variable, that variable is your answer, and unsetting it is an organizational decision rather than a local one when it arrives through managed settings. The same line explains why Remote Control, /import, and the advisor tool are missing on that machine, since they sit behind the same fetch.

One more limit: if you run /skill-doctor over Remote Control from a phone or browser, Claude Code replies Skill usage reports are not available on this connection. Run it in the terminal on the machine hosting the session, or use the -p form there.

A note on the version number

The docs say v2.1.252 or later. The changelog entry that announces the command is 2.1.261, dated September 4, 2026, and the 2.1.252 entry (August 31) does not mention it. The most likely reading is that the command shipped behind a flag and the docs quote the first build that can evaluate it, while the changelog quotes the release where it was turned on. We are reporting the discrepancy rather than resolving it: if you are on 2.1.261 or later with feature flags fetching, you have it.

Three levers once you have the numbers

Once you know which skills cost the most and get used the least, there are three places to act, and they differ by who owns the skill file.

1. skillOverrides, for skills you do not want to edit

The skillOverrides setting controls a skill's visibility from your settings file instead of its frontmatter, which is the right tool for skills checked into a shared repo. Each key is a skill name and each value is one of four states:

ValueListed to ClaudeIn the / menu
"on"Name and descriptionYes
"name-only"Name onlyYes
"user-invocable-only"HiddenYes
"off"HiddenHidden

"name-only" is the one to reach for first. It frees the description's share of the listing budget while leaving the skill invocable by name, so a skill you call explicitly once a month stops competing with the ones Claude should pick on its own. A skill absent from skillOverrides is treated as "on".

{
  "skillOverrides": {
    "legacy-migration-notes": "name-only",
    "deploy-to-staging": "user-invocable-only",
    "old-release-checklist": "off"
  }
}

You do not have to type this. The /skills menu writes it for you: highlight a skill, press Space to cycle through the four states, and press Esc to save to .claude/settings.local.json. The menu will not cycle plugin skills, skills whose frontmatter sets disable-model-invocation: true, or skills that already have a skillOverrides entry in managed settings or via --settings.

2. Frontmatter, for skills you own

If the SKILL.md is yours, add disable-model-invocation: true to the frontmatter. That removes the skill from Claude's context entirely: it no longer appears in the listing, and only you can invoke it, by typing its name. This is the natural setting for skills that are really commands, such as a release procedure you never want triggered by a description match.

The other frontmatter fix is the description itself. Since each entry is capped at 1,536 characters and the whole listing at one percent of the window, a description that opens with the trigger phrase and stops is worth more than one that explains the skill's history. Our skills tutorial has the field-by-field detail.

3. /plugin, for plugin skills

skillOverrides does not affect skills that come from plugins. For those, the unit of control is the plugin: /plugin disable <name> or an uninstall from the Installed tab. The Not used recently header and the Last used line on each plugin's detail view are the evidence for that decision, and /skill-doctor surfaces the same list.

Raising the budget instead

Sometimes the right answer is not fewer skills but a bigger listing. Two knobs:

  • skillListingBudgetFraction in any settings file, for example 0.02 for two percent of the window instead of one.
  • SLASH_COMMAND_TOOL_CHAR_BUDGET as an environment variable, for a fixed character count instead of a fraction.

And one to lower if you would rather fit more skills under the same budget: skillListingMaxDescChars, which caps each entry's text (default 1,536).

{
  "skillListingBudgetFraction": 0.02,
  "skillListingMaxDescChars": 1024
}

Raising the budget is a real cost on every request, so treat it as the last resort after name-only and pruning. The docs also note that when the listing exceeds its budget, Claude Code writes a warning to the debug log, visible with --debug; if you have never looked, that is a quick way to find out whether you are already over.

/skill-doctor is new, but it sits in a small family of context diagnostics that already existed, and it helps to know which to run when.

  • /context shows everything occupying the window by category, including a Skills row. Since v2.1.196 that row reports the size of the listing after the budget is applied, so it matches what the model receives. Before that it counted every description in full and could read several times larger than the configured budget.
  • /skills lists your skills from project, user, and plugin sources. Press t to sort by token count. This is where you cycle skillOverrides states.
  • /doctor is the bundled setup-checkup skill. Among other things it estimates the listing's context cost and names its biggest contributors, and it finds unused skills, MCP servers, and plugins versus their cost. It reports first and asks before changing anything. /skill-doctor is the narrower, faster report on skills alone.
  • /usage (alias /cost) attributes recent usage to skills, subagents, plugins, and MCP servers as percentages of the total, which answers a different question: not what a skill costs to list, but how much of your spend went through it once invoked.

A skill's cost does not end with the listing, either. When a skill is invoked, its rendered body enters the conversation and stays there for later turns. Across auto-compaction, Claude Code re-attaches the most recent invocation of each skill after the summary, keeping the first 5,000 tokens of each within a combined 25,000-token budget, most recent first. Long skill bodies therefore have a second cost that /skill-doctor does not measure, and it is another reason to keep instructions tight.

What we do with this in our own repos

We ship skills for a living, so this is the checklist we now run on a pack before publishing, and it is a reasonable one for a team's shared .claude/skills directory too.

  1. Run the listing-size script above and look at the total. Anything over a few hundred characters per skill is a description that is doing the body's job.
  2. Open /skills, press t, and read the top five by token count. Ask whether each one earns its place on every turn.
  3. Where /skill-doctor is available, run it in a session that has done a normal week's work, not a fresh one, so the invocation counts mean something. Set never-invoked skills to name-only first, off second.
  4. For anything you invoke only by name, set disable-model-invocation: true in the frontmatter and take it out of the listing altogether.
  5. Re-run /context and confirm the Skills row moved.

FAQ

Does a skill I never use still cost tokens?

Yes. Its name and description are part of the skill listing Claude Code sends on every turn, up to the listing budget. The body of the skill does not load until the skill is invoked, so the per-turn cost is the description, not the whole file. /skill-doctor reports that cost per skill, and /skills shows token counts if you press t.

Why did a skill I use all the time stop triggering after I added more?

Most likely the listing went over its budget. Claude Code keeps every skill's name but drops descriptions starting with the least-invoked skills, and a skill with no description in the listing has nothing for Claude to match your request against. Check the Skills row in /context, run /doctor for the biggest contributors, then either set low-value skills to name-only or raise skillListingBudgetFraction.

What is the difference between /doctor and /skill-doctor?

/doctor is a broad setup checkup: installation health, invalid settings files, slow hooks, oversized CLAUDE.md files, and unused skills, MCP servers, and plugins against their context cost, with proposed fixes it asks to apply. /skill-doctor is a focused report on skills and plugins only: cost, invocation frequency, never-invoked skills, and where to turn them off. It does not change anything.

Why do I get "Unknown command: /skill-doctor"?

Either Claude Code is older than 2.1.252, or the session is not fetching feature flags. Run claude doctor and look for a line reading "Feature-flag evaluation disabled"; it names the cause, such as DO_NOT_TRACK or DISABLE_TELEMETRY. Sessions on Amazon Bedrock, Google Cloud's Agent Platform, Microsoft Foundry, Claude Platform on AWS, and the Claude apps gateway do not fetch flags either.

Does skillOverrides work on plugin skills?

No. Plugin skills are managed at the plugin level through /plugin, where you can disable or uninstall the plugin. The /skills menu will not cycle a plugin skill's state for the same reason.

Can I run /skill-doctor from my phone over Remote Control?

No. Claude Code does not send the report over Remote Control and replies Skill usage reports are not available on this connection. Run it in the terminal on the machine hosting the session, or run claude -p "/skill-doctor" there to get the report as text.