anthropics / anthropics/claude-code
Skill auto-discovery is exactly one level deep, but the docs say "all SKILL.md files in skill subdirectories" — and a nested SKILL.md is ignored silently
- Langage dominant
- Python
- Étoiles
- 145k
- Forks
- 23.1k
- Métriques de merge des PR
- Métriques de PR en attente
Description
### What the docs say
[`plugins/plugin-dev/skills/plugin-structure/SKILL.md`](https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/plugin-structure/SKILL.md) states:
> **Auto-discovery**: All SKILL.md files in skill subdirectories load automatically
Read literally, that promises recursive discovery. It isn't — discovery is
exactly one level, and the example directly beneath the sentence only ever
shows the one-level shape, so the mismatch is easy to miss.
### What actually happens
Measured on **CLI 2.1.238** by reading the `skills` array off the `init`
message (i.e. what the CLI actually registered — not what a model reports it
can see):
| Path under `skills/` | Registered? |
|---|---|
| `flatone/SKILL.md` | ✅ `nestprobe:flatone` |
| `group/SKILL.md` | ✅ `nestprobe:group` |
| `namecheck/SKILL.md` | ✅ `nestprobe:namecheck` |
| `imessage/chats/SKILL.md` | ❌ |
| `group/child/SKILL.md` | ❌ |
| `deep/er/still/SKILL.md` | ❌ |
Same result for project skills in `.claude/skills/` — the one-level rule holds
for both discovery paths.
Two details worth calling out:
- `group/` registers **and** `group/child/` does not. An intermediate directory
is not a container: it is either a skill itself (it has its own `SKILL.md`)
or nothing, and either way its subdirectories are never scanned.
- The skip is **completely silent**. No `plugin_errors`, no `plugin_warnings`,
nothing on stderr. A misplaced `SKILL.md` just never exists, which is what
makes this cost people real time.
### Reproduction
```bash
# plugin with skills at four different depths
mkdir -p p/.claude-plugin
cat > p/.claude-plugin/plugin.json <<'JSON'
{"name":"nestprobe","version":"0.0.1","description":"depth probe"}
JSON
for path in flatone group imessage/chats group/child deep/er/still; do
mkdir -p "p/skills/$path"
printf -- '---\nname: %s\ndescription: probe\n---\n# probe\n' "$(basename "$path")" \
> "p/skills/$path/SKILL.md"
done
# ask the CLI what it registered
echo '{"type":"user","message":{"role":"user","content":"hi"}}' \
| claude --input-format stream-json --output-format stream-json --verbose \
--model haiku --setting-sources "" --plugin-dir ./p 2>/dev/null \
| python3 -c 'import sys,json
for l in sys.stdin:
o=json.loads(l) if l.strip() else {}
if o.get("subtype")=="init":
print([s for s in o.get("skills",[]) if "nestprobe" in str(s)]); break'
```
Prints `['nestprobe:flatone', 'nestprobe:group']` — the three nested skills are
absent.
(Note for anyone reproducing: asking the model to "list every skill available
to you" is not a reliable check. If it has `Glob`/`Read` it can enumerate the
plugin directory off disk and report nested skills that were never registered,
which yields a false positive. The `init` registry is the authoritative source.)
### Suggested fixes
1. **Fix the sentence.** Something like: "Auto-discovery: each immediate
subdirectory of `skills/` containing a `SKILL.md` loads automatically.
Nested subdirectories are not scanned." One line, and this class of
confusion goes away.
2. **Warn on a skipped `SKILL.md`.** When discovery finds a `SKILL.md` below
depth 1, surface it — `plugin_warnings` already exists on the `init`
message and is exactly the right channel. Silence is the expensive part:
the author's mental model is "I wrote a skill and Claude ignores it", with
nothing to grep for.
If recursive discovery is intentionally out of scope (name collisions across
nested groups being the obvious reason), then the doc fix alone resolves it —
symlinking a nested directory to depth 1 works today as a workaround and could
be mentioned.
### Secondary, same doc page: directory name wins over frontmatter `name:`
A skill in `namecheck/` declaring `name: totally-different` in its frontmatter
registers as **`namecheck`** — the directory name. Verified for both plugin
skills (`nestprobe:namecheck`) and project skills.
Every example on the page has the two agreeing, so which one is authoritative
is never stated. Worth one sentence, since a mismatch silently resolves to the
directory and the `name:` field appears to have no effect on the registered
identifier.
Guide de contribution
Aucun guide de contribution indexé pour ce dépôt
Piste de recherche
Start with plugins/plugin-dev/skills/plugin-structure/SKILL.md and run the supplied plugin-discovery reproduction, checking the init message rather than model-reported skills. Update the documentation to describe immediate-subdirectory discovery, nested SKILL.md behavior, and directory-name precedence; done when those documented rules match the observed registry.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- python, shell
- Domaine
- cli, documentation
- Type d'issue
- Documentation
- Difficulté
- 2/5
- Temps estimé
- 1-3 heures
- Activité
- Active
- Clarté
- Plutôt claire
- Accessibilité débutants
- 72/100