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

オープン 初心者向け
#88,400 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る
area:docs area:plugins area:skills bug has repro
主要言語
Python
スター
145k
フォーク
23.1k
PR マージ指標
PR 指標を取得中

説明

### 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.

コントリビューションガイド

このリポジトリのコントリビューションガイドは索引されていません

調査の方向性

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.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python, shell
領域
cli, documentation
issue の種類
ドキュメント
難易度
2/5
見積もり時間
1〜3時間
活発さ
活発
明瞭さ
おおむね明確
初心者へのやさしさ
72/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。