A doctor for your Claude Code agent skills: find the SKILL.md files Claude silently can't see (over the discovery budget), plus naming, routing and hygiene problems — deterministically, across every installed skill.
Project description
claude-skills-doctor
A doctor for your Claude Code agent skills — find the ones Claude silently can't see, before they cost you a session.
Install
claude-skills-doctor, runskilldoctor.
You install a skill, it looks perfect, and Claude never uses it. No error, no warning.
Claude Code loads a listing of skill names and descriptions into context so Claude knows what exists. The listing always keeps every skill name — but when it overflows, Claude Code drops descriptions, starting with the skills you invoke least. A skill with no description is a skill Claude has nothing to match your request against, so it quietly stops being chosen. Two limits squeeze that listing, and they fail differently:
- Per entry —
description+when_to_useis truncated at 1,536 characters, regardless of budget. Exact, and the same on every model. - Across the listing — the total is 1% of your model's context window, raised with
skillListingBudgetFractionor pinned withSLASH_COMMAND_TOOL_CHAR_BUDGET.
That second one is why a fixed number would mislead you: the same 11,000 characters of skills is comfortable on a 1M-token model and 37% over on a 200K one.
$ skilldoctor
Skill listing budget
████████████████████████████████ 10,990 / 8,000 chars (137%)
budget from 1% of a 200,000-token context window, at ~4 chars/token (estimated)
! over budget - names still list, but Claude Code drops descriptions to fit, least-used first
severity check target detail
error name-folder-mismatch pdf-tools `name: pdf` must match the folder name `pdf-tools`
error frontmatter-invalid my-skill invalid YAML in frontmatter: mapping values are not allowed here
warn description-capped research description + when_to_use is 1,904 chars — the listing truncates each entry at 1,536, so the last 368 chars never reach Claude.
warn description-collision review-code description ~72% overlaps `code-review` — they may compete to trigger
28 skill(s) · 6 command(s) · 2 error(s) · 2 warning(s)
It reads your actual settings rather than assuming defaults — skillListingBudgetFraction (so raising your own budget is respected), skillListingMaxDescChars, and skillOverrides (a skill you already set to name-only costs its name, and one set to off costs nothing). Pass --context-window 1000000 for your model, or --budget N to pin it exactly. The tool always says which number it used and whether it was measured or derived.
Install
uvx claude-skills-doctor # run without installing
pip install claude-skills-doctor # or install it
Then run skilldoctor (the command claude-skills-doctor works too).
Use
skilldoctor # health-check every skill Claude Code can see; exits non-zero on errors
skilldoctor --fix # show a concrete suggested fix for each finding
skilldoctor --json # machine-readable, for CI
skilldoctor --strict # fail on warnings too
skilldoctor budget # the shareable view: the bar + the biggest contributors to trim
It scans everywhere Claude Code loads from — your user skills (~/.claude/skills), the project's (.claude/skills), and installed plugins — plus slash-commands, which share the same budget.
Or ask Claude
It also ships as a Claude Code plugin, so the diagnosis lives where the problem shows up — you notice a skill isn't firing, you ask, and Claude checks:
/plugin marketplace add gulmezeren2-byte/claude-skills-doctor
/plugin install skilldoctor@skilldoctor
The plugin ships one skill and no slash command. A tool that measures the listing budget shouldn't take two slots out of it.
Authoring skills? Lint what you ship
--skills points at a skills/ directory you're writing, so you can catch problems before publishing rather than after someone installs them:
skilldoctor --skills ./skills --strict # fails on warnings too — good CI gate
This repo does exactly that to itself: CI runs skilldoctor --skills ./skills --strict against the skill it ships, and a test asserts it. If our own tool would flag our own skill, the build fails.
In GitHub Actions
- uses: gulmezeren2-byte/claude-skills-doctor@v1
with:
skills: ./skills # omit to check the repo's .claude/skills
strict: true # fail on warnings too
Nothing to install — the action fetches the tool for you. Set json: true and the report comes back as a parseable report output; the step still fails the job when there are errors, so the exit code stays the gate and the JSON is just data. budget and version are there when you need to pin either.
What it does in your CI: installs uv, downloads this package from PyPI, reads your skill files, prints a report, exits. It reads no secrets, writes nothing to your repo, sends no telemetry, and makes no network calls beyond installing itself.
If you want it fully deterministic, pin both halves — the action by commit and the tool by version:
- uses: gulmezeren2-byte/claude-skills-doctor@<commit-sha>
with:
version: "0.7.0"
@v1 is a moving tag, and without version the tool resolves to the latest release, so an unpinned setup follows upstream. That's the usual trade-off: pinned is reproducible, unpinned gets fixes. The action pins its own dependency (setup-uv) by commit for the same reason. Releases are published to PyPI from CI with Trusted Publishing (OIDC) and carry attestations — there's no long-lived token to steal.
What it checks
All deterministic. No model calls, no network, no guessing — it reads your files and reports.
- Listing budget — the headline. Total skill + command listing characters against 1% of your model's context window (or
SLASH_COMMAND_TOOL_CHAR_BUDGET/--budget, which are exact). Over budget → descriptions get dropped, least-used first. - Per-entry cap —
description+when_to_useover 1,536 characters is truncated in the listing whatever the budget. Exact, model-independent, and easy to cross without noticing. - Won't load — invalid YAML frontmatter (which silently prevents loading), missing
name/description, andnamethat doesn't match its folder (required, and the one Anthropic's own validator misses). - Won't route — descriptions too thin to trigger, and descriptions written as a step-by-step how-to (which makes Claude follow the summary and skip loading the body).
- Collisions — two skills with the same
name, or near-identical descriptions that compete to trigger. - Contract & hygiene —
allowed-toolsthat doesn't cover the tools the body actually uses,</>in descriptions (prompt-injection risk), oversized bodies that belong inreferences/, reference chains more than one level deep, absolute paths, and human-facing docs (README/CHANGELOG) left inside a skill folder. - Security — scans every skill (third-party plugins included) for
hidden-instructionphrases (instruction-override / hide-from-user / prompt-extraction) in the SKILL.md and its references, andscript-network-calls insidescripts/. Because "I installed a skill — is it safe?" is a real question, and a documentation-context filter keeps skills that teach prompt-injection safety from tripping it.
Errors make it exit non-zero, so it gates a pipeline: skilldoctor && claude .... Add --fix to see a concrete suggested fix under each finding.
How it's different from a skill linter
There are per-file SKILL.md linters and validators (and Anthropic's skill-creator model-evaluates one skill you're authoring). claude-skills-doctor is the complement: a whole-system, install-time health check across every skill you actually have — and the only one that measures the cross-skill failures a single-file linter structurally can't see: the shared listing budget, duplicate names, and colliding descriptions. A linter checks a file; a doctor examines the whole patient.
Honest about the numbers
The budget is measured as each skill/command's name + description characters. That's an estimate — the real budget adds minor per-entry formatting — so treat it as close, and on the safe side, not byte-exact. It lints your own skills fully; third-party plugin skills count toward the budget but are checked only for load-breakers, so it never nags about skills you can't fix. Every finding points at a file and a reason; nothing is invented.
Related
- mcp-contract — the same idea one layer down.
skilldoctorwatches the skills an agent can see — are they under the listing budget, do their descriptions collide?mcp-contractwatches the tools it calls — did a change to a server's tool surface quietly break the callers? Both treat the text an agent routes on as a contract worth testing.
More tools by Eren Gülmez.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_skills_doctor-0.7.0.tar.gz.
File metadata
- Download URL: claude_skills_doctor-0.7.0.tar.gz
- Upload date:
- Size: 80.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f65dcd47566102ebe060143d22310af6451f1881cc5549dd23b6df4c5be806e2
|
|
| MD5 |
abaa9223e80b0d8aa403c6fd15b40e77
|
|
| BLAKE2b-256 |
b7c8dd01e296c5df2cf832453fecda4137b5c0875420455f30f93e2c279c7ec7
|
Provenance
The following attestation bundles were made for claude_skills_doctor-0.7.0.tar.gz:
Publisher:
release.yml on gulmezeren2-byte/claude-skills-doctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_skills_doctor-0.7.0.tar.gz -
Subject digest:
f65dcd47566102ebe060143d22310af6451f1881cc5549dd23b6df4c5be806e2 - Sigstore transparency entry: 2302984361
- Sigstore integration time:
-
Permalink:
gulmezeren2-byte/claude-skills-doctor@57efe329a61015665e2cf266080481670b9e47ca -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/gulmezeren2-byte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@57efe329a61015665e2cf266080481670b9e47ca -
Trigger Event:
push
-
Statement type:
File details
Details for the file claude_skills_doctor-0.7.0-py3-none-any.whl.
File metadata
- Download URL: claude_skills_doctor-0.7.0-py3-none-any.whl
- Upload date:
- Size: 32.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
dc010a7ecaca7288e31ed61e590d7900e6f3cb83016670483187cb182a34b71e
|
|
| MD5 |
b9a3dcf338f8704824f367dfa64ee404
|
|
| BLAKE2b-256 |
13546459f063d45a0cea95cdbe2bae41403588e8b90a9cc5c47fc5f16b4bb13f
|
Provenance
The following attestation bundles were made for claude_skills_doctor-0.7.0-py3-none-any.whl:
Publisher:
release.yml on gulmezeren2-byte/claude-skills-doctor
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
claude_skills_doctor-0.7.0-py3-none-any.whl -
Subject digest:
dc010a7ecaca7288e31ed61e590d7900e6f3cb83016670483187cb182a34b71e - Sigstore transparency entry: 2302984433
- Sigstore integration time:
-
Permalink:
gulmezeren2-byte/claude-skills-doctor@57efe329a61015665e2cf266080481670b9e47ca -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/gulmezeren2-byte
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@57efe329a61015665e2cf266080481670b9e47ca -
Trigger Event:
push
-
Statement type: