Back to posts
AINews

57 of 65 loaded skills had never run: audit the listing, then pin the install

Claude Code 2.1.261 added /skill-doctor, which lists every loaded skill with a usage count and what its listing costs in context. Run on a working machine it found 65 skills, 57 of which had never been invoked once, carrying about 6,400 tokens of system prompt on every turn. The other half of the problem is provenance: a skill installed into ~/.claude/skills records nothing about where it came from. The skills CLI added commit-SHA pinning on September 6, with two constraints that will silently cost you the pin.

A skill is instructions, and often executable code, that runs with whatever permissions your agent has. Most people acquire them the way you acquire browser extensions: one at a time, from a link, and never again thought about. Two questions follow from that, and until this month neither had a good answer on the command line. What am I carrying, and what does it cost me? And where did it come from, and can it change without my doing anything?

Both now have one. Both answers have a catch worth knowing before you rely on them.

The inventory

Claude Code 2.1.261, released September 4, added /skill-doctor. The changelog line describes it as showing "which loaded skills go unused and what they cost in context, so you can prune them."

Run on a working machine — 51 directories under ~/.claude/skills plus 14 skills from three installed plugins — it returned 65 rows. Eight had been invoked at least once. The other 57 had never run:

  skill                                 source           context  7d tokens   uses  last used
  higgsfield-marketplace-cards          userSettings        ~240          -     0×  never
  imagegen-frontend-web                 userSettings        ~230          -     0×  never
  seo-dataforseo                        userSettings        ~210          -     0×  never
  ...
  design-taste-frontend                 userSettings        ~100          -     5×  22 days
  mmx-cli                               userSettings         ~80          -     4×  29 days
  seo                                   userSettings        ~170          -     1×  91 days

43 skills loaded but never invoked. Each one adds to the system prompt every turn.
Disable in /skills, or remove from .claude/skills.
14 plugin skills loaded but never invoked, from cloudflare, frontend-design. Each one
adds to the system prompt every turn. Plugin skills can't be turned off individually —
disable those plugins in /plugin.

Summing the context column: the full listing is roughly 7,900 tokens of system prompt sent on every turn, and about 6,400 of that belongs to skills that have never run once. Roughly 81 percent of what the machine pays to advertise its skills is spent advertising skills it does not use. The eight that do get used were last invoked between 22 and 91 days ago.

What that number is, and what it is not

The context column is not the skill body, and reading it as though it were will send you deleting things you should keep. /skill-doctor prints the definition under the table:

context = this skill's one-line listing in the system prompt, included every turn (dash = not in the current listing, costs nothing; full SKILL.md loads only when it runs)

The skills documentation states the same rule in a table: for a default skill, the "description [is] always in context, full skill loads when invoked." So a 2,000-line SKILL.md you never trigger costs you its description line and nothing more. The expensive thing is not the skill. It is the advertisement for the skill.

That reframes the fix. You are not trying to shrink files. You are trying to stop announcing capabilities you do not use — which also matters for a reason the token count does not show. Every description in that listing is a candidate the model weighs when it decides whether a skill applies. Sixty-five candidates is a harder selection problem than eight, and the ones that never fire are pure noise in it.

This is the per-skill view that a general context audit does not give you. If you have not done the broader pass, cut the tokens Claude Code spends before it reads your prompt covers the instruction file, MCP servers, and tool output; /skill-doctor is the instrument for the skills line specifically, and it reports invocation counts and last-used dates rather than a share of recent usage.

Three ways to take a skill out of the listing

They are not equivalent, and the first one is the one most people do not know about.

Keep the skill, remove the advertisement. Add one line to the skill's frontmatter:

---
name: industrial-brutalist-ui
description: Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics...
disable-model-invocation: true
---

The documentation's table says this changes the skill to "description not in context, full skill loads when you invoke." Measured on the machine above, before and after, the row moved from ~100 to -, and the summary count dropped from 43 skills never invoked to 42:

before:  industrial-brutalist-ui   userSettings   ~100   -   0×   never
after:   industrial-brutalist-ui   userSettings      -   -   0×   never

The skill is still installed and still runs when you type /industrial-brutalist-ui. It has simply stopped taking up room in every request. For anything you use occasionally but deliberately, this is the correct setting, and it is reversible by deleting one line.

Disable it in the menu. /skills turns a skill off without editing files.

Delete the directory. ~/.claude/skills/<name>/ or .claude/skills/<name>/. Appropriate for things you installed to try once.

Plugin skills are the exception, and /skill-doctor says so in its own output: they "can't be turned off individually — disable those plugins in /plugin." On the machine above, 14 of the 57 unused skills came from plugins, so a bit under a quarter of the waste was not addressable one skill at a time. If a plugin ships twenty skills and you want two, the current choice is all twenty or none.

The second question: where did any of this come from

Run this against your own skills directory:

find ~/.claude/skills -maxdepth 2 -name ".git" | wc -l

On the machine above the answer was 0. No skill directory carried a git repository, a version, a source URL, or an install date. The frontmatter of an installed skill holds name and description, and that is the whole record. There is no command that will tell you which of those 51 directories came from which repository, or at what commit, because nothing wrote it down.

The plugin system, by contrast, does keep a record. ~/.claude/plugins/installed_plugins.json stores a resolved commit for each plugin:

vercel@claude-plugins-official           v0.48.0          sha=61f1903bed7b
frontend-design@claude-plugins-official  v85cce0381e78    sha=0120fb83da5d
cloudflare@cloudflare                    v1.0.0           sha=30553f89ae1e

That is the difference worth internalising. A plugin you installed is pinned to a commit you can look up. A skill you installed is a folder of instructions with no history, and if you fetched it from a branch, the next update pulls whatever that branch says then.

Pinning an install to a commit

The skills CLI (30,900 stars, MIT) shipped v1.5.24 on September 6 at 22:48 UTC, adding installs pinned to a commit SHA. You pin by putting the ref in the URL:

npx skills add https://github.com/vercel-labs/agent-skills/tree/063bee94c3f4df8453406c830b0a7df0f2860278/skills/web-design-guidelines

Before this release, that failed. PR #1439 explains why: the installer passed the ref to git clone --depth 1 --branch <ref>, and --branch accepts branch and tag names only, never a bare commit. Tags could be pinned; commits could not, which as the PR puts it "blocks reproducible installs from repos that do not cut tags, where a SHA is the only stable pin." The fix tries --branch first and, when that reports a missing ref, falls back to git init plus git fetch --depth 1 origin <sha> and git checkout FETCH_HEAD.

Constraint one: the full 40 characters. The gate on that fallback is a single regular expression in src/git.ts:

export function isCommitSha(ref: string): boolean {
  return /^[0-9a-f]{40}$/i.test(ref);
}

An abbreviated SHA — the seven or ten characters you get from git log --oneline, or from clicking a commit in the GitHub interface — does not match, so the fallback never runs. The repository's own tests assert this: isCommitSha('6c6076a') is false, and so is isCommitSha('6c6076a293'). Paste a short SHA and you do not get a shorter pin, you get a failed install.

Constraint two: the pin fails if your git does not speak English

This one is worth testing on your own machine before you trust a pinned install, because the reason is invisible in the error.

The fallback triggers on the text of the git error. isMissingRefError matches three strings:

/Remote branch .* not found in upstream origin/i
/couldn't find remote ref/i
/upload-pack: not our ref/i

The source comment reasons that these come from the git client and the server rather than the host, and are therefore "identical across GitHub, GitLab, self-hosted git, and gh". That holds across hosts. It does not hold across languages. Git localises these messages, and on a machine whose system language is not English, git emits the translated string, no pattern matches, and the SHA fallback never fires.

On a macOS machine with git 2.53 and the system language set to Swedish, the exact command above fails:

◇  Falling back to clone…
■  Failed to clone repository
│  ödesdigert: Fjärrgrenen 063bee94c3f4df8453406c830b0a7df0f2860278
│  hittades inte i uppströmsarkivet origin
└  Installation failed

The same command, with the locale forced, installs:

LC_ALL=C npx skills add https://github.com/vercel-labs/agent-skills/tree/063bee94c3f4df8453406c830b0a7df0f2860278/skills/web-design-guidelines
◇  Installation complete
✓ web-design-guidelines (copied)
  → ./.claude/skills/web-design-guidelines

LC_MESSAGES=C and LANG=C each work as well; any of the three restores the English error text the matcher expects. Note that LANG and LC_ALL were both unset in the shell where this failed — git on macOS picks up the system language from defaults read -g AppleLanguages, so an empty environment is not a guarantee of English output.

What makes this worth a paragraph rather than a footnote is the shape of the failure. Installing the same skill from a branch succeeds in the identical locale, because --branch main resolves on the first attempt and no error text ever needs parsing. So the pin is the only thing that breaks, the message you see is a clone failure, and the conclusion a reasonable person draws is that SHA pinning does not work yet. Adding LC_ALL=C is not an obvious next move when nothing in the output mentions language.

The pass, end to end

  1. npm view @anthropic-ai/claude-code dist-tags/skill-doctor arrived in 2.1.261. As of today stable sits at 2.1.236, published August 20, and latest at 2.1.263. On the stable channel the command is not there.
  2. Run /skill-doctor. Read the uses and last used columns before the token column.
  3. For anything at that you still want: add disable-model-invocation: true. For anything you will not use: delete it. For plugin skills, decide at the plugin level in /plugin.
  4. find ~/.claude/skills -maxdepth 2 -name ".git" | wc -l to see how much of your installed set has any provenance at all.
  5. Re-install anything you care about with a full 40-character SHA in the /tree/ path, and confirm the skill appeared on disk afterwards rather than trusting the exit code. If the clone fails, retry with LC_ALL=C before concluding the pin is unsupported.

The order matters more than it looks. Pruning first makes the pinning job smaller, and on the machine measured here, step 3 would have removed 57 of the 65 entries that step 5 would otherwise have to account for.

Related: Agent Plugins 1.0 lets you package a skill or MCP server once for the distribution side, what an agent sandbox rule does not cover for what a skill can reach once it runs, and the skill packs if you would rather start from a curated set than a search result.

Get the next post when it ships

One email on Sunday with the new post and a short list of what shipped that week — new guides, tool updates, and a couple of links worth reading.