[BUG] `claude plugin validate` silently validates zero skills when the manifest's skills path is `./.claude/skills` — no enumeration, no warning, exit 0
Summary
claude plugin validate (incl. --strict) validates zero skills when a plugin manifest's skills array points at ./.claude/skills — it prints ✔ Validation passed (exit 0) with no Validating skill: lines, no "0 skills found" notice, and no warning that the configured path was not enumerated. The identical skill files under a ./skills path are enumerated and validated normally.
The silence is the defect: a plugin author who configures a path the validator does not enumerate gets a green check that has never looked at a single skill, and --strict's documented promise ("Use in CI to fail on … issues that the runtime tolerates") is structurally unfulfillable for that plugin.
Environment
- Claude Code CLI
2.1.232, macOS (darwin 25.5.0)
Reproduction (minimal, with a control)
Both variants contain the byte-identical control skill — a SKILL.md deliberately missing description, i.e. something the validator demonstrably warns about when it looks:
repro-a/
.claude-plugin/plugin.json → "skills": ["./.claude/skills"]
.claude/skills/ctrl/SKILL.md → frontmatter with name only, no description
repro-b/
.claude-plugin/plugin.json → "skills": ["./skills"]
skills/ctrl/SKILL.md → identical bytes
plugin.json (variant A; B differs only in the skills value):
{
"name": "repro-a",
"version": "0.0.1",
"description": "repro: dotclaude skills path",
"author": { "name": "repro" },
"skills": ["./.claude/skills"]
}
Actual output
Variant A (./.claude/skills):
Validating plugin manifest: …/repro-a/.claude-plugin/plugin.json
✔ Validation passed
exit 0 — no Validating skill: line at all; the defective control skill is never seen.
Variant B (./skills):
Validating plugin manifest: …/repro-b/.claude-plugin/plugin.json
Validating skill: …/repro-b/skills/ctrl/SKILL.md
⚠ Found 1 warning:
❯ description: No description in frontmatter. A description helps users and Claude understand when to use this skill.
✘ Validation failed (--strict treats warnings as errors)
exit 1 — enumerated and caught, as expected.
Expected
Either of these would close the trap; the first alone would already surface it:
- A configured
skillsentry that yields zero skills produces a loud warning (and a--strictfailure) — "skills path./.claude/skillsmatched no skills" — instead of silence. A validator run that inspected nothing should never be indistinguishable from one that inspected everything and found it clean. - Ideally, the enumerator also handles the
./.claude/skillslayout, since the runtime does load skills from it (see impact below) — validate and runtime currently disagree about the same manifest.
Real-world impact (how this stayed invisible)
Our published plugin (formtrieb/flotilla, manifest uses "skills": ["./.claude/skills"]) ships 15 skills that load and run fine at runtime. Five of them carry frontmatter the validator's own YAML parser rejects outright (YAML frontmatter failed to parse — unquoted description: values containing : , invalid as a YAML plain scalar, which the runtime parser tolerates). Every claude plugin validate run over the real manifest printed Validation passed — the parse failures only surfaced when we pointed the validator at the skills via a path form it enumerates. So the silent zero-coverage hid a real, validator-detectable defect class in a shipped plugin for weeks.
Related but distinct existing reports, for triage context: #77912 (validate passes hook commands referencing missing files), #82749 (marketplace git-sync silently drops invalid skills), #76448 (desktop loader ignores the skills array). This one is specifically: validate never enumerates the configured ./.claude/skills path and stays silent about it.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗