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

Ouverte Adaptée aux débutants
#88,400 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
area:docs area:plugins area:skills bug has repro
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

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.