fix(plugin): strict validation skips malformed skill and agent files
Summary
claude plugin validate --strict --json reports success and returns an empty contents array for directories containing malformed skill or agent YAML frontmatter. This also occurs when validating a plugin with a valid manifest and the conventional skills/ layout. A CI job relying on the exit code accepts these malformed components without any per-file result or warning.
Steps to reproduce
Use the native CLI described under Runtime or environment. No authentication or model request is needed. In an empty temporary directory, create these files:
mkdir -p plugin/.claude-plugin plugin/skills/bad project/.claude/agents
cat > plugin/.claude-plugin/plugin.json <<'JSON'
{"name":"validation-probe","version":"1.0.0","description":"Harmless validation fixture","author":{"name":"Fixture"},"skills":"./skills"}
JSON
cat > plugin/skills/bad/SKILL.md <<'SKILL'
---
name: [
description: malformed skill
---
Harmless broken fixture.
SKILL
cat > project/.claude/agents/probe.md <<'AGENT'
---
name: [
description: malformed agent
---
Harmless broken fixture.
AGENT
claude plugin validate ./plugin --strict --json
printf 'exit=%s\n' "$?"
claude plugin validate ./plugin/skills --strict --json
printf 'exit=%s\n' "$?"
claude plugin validate ./project/.claude/agents --strict --json
printf 'exit=%s\n' "$?"
The unclosed name: [ sequence is invalid YAML. All entries are ordinary files and directories. The reported results also reproduce with canonical paths and an isolated CLAUDE_CONFIG_DIR.
Expected behavior
The validator should inspect the component files, include their results in contents, report the malformed YAML, and exit nonzero. The documented directory selection includes files inside a directory named skills or agents. Strict mode should not return success after silently checking no component files in these populated directories.
Actual behavior
Each of the three commands exits 0, writes no stderr, and reports success: true, strict: true, and contents: []. For the plugin-root command, manifest contains a successful plugin manifest result. For the two component-directory commands, manifest is null.
Representative component-directory output follows. The absolute target path is replaced with the relative fixture path.
{
"success": true,
"strict": true,
"target": "./plugin/skills",
"manifest": null,
"contents": []
}
Replacing the malformed skill frontmatter with a valid name and description still produces no per-file results. As a separate control, changing the plugin manifest to {"name":123} produces success: false, a manifest error saying Invalid input: expected string, received number, and exit 1. This distinguishes skipped component validation from a command that never performs validation.
Evidence
- Expected source: The installed command's
plugin validate --helpsays it validates "the skills, agents, and commands in a directory." The official CLI reference, Validate a directory and Output and exit codes describes the directory selection and per-filecontentsresults. - Failure source: Executed the three commands above with a freshly downloaded native artifact, ordinary copied fixtures, canonical paths, and isolated configuration. Their outputs and exit codes are reproduced under Actual behavior.
- Evidence provenance: observed
- Local verification: reproduced
- Reproduction completeness: complete
An independent YAML parser also rejects the skill's frontmatter with did not find expected ',' or ']' while parsing a flow sequence.
Runtime or environment
- Claude Code version:
2.1.292 (Claude Code), nativedarwin-arm64, macOS 27.0. - Official native artifact manifest commit:
37832d0b7cad7b40bac7c82dff58629313913edf. The downloaded binary's SHA-256 matched the official manifest. - Latest published GitHub release and native download channel both matched the tested version at verification.
- Fresh public repository default branch
main:fbe20e00e2851fc01506f54f98a8f0b875af3847. The repository publishes examples and release notes; the full CLI implementation is not public, so this is a release-artifact reproduction rather than a source build. - Platform: Other, local CLI validation with no API request. Model: not applicable.
- Terminal/shell: Non-interactive environment, zsh.
- Regression status: unknown. No earlier release was tested in this investigation.
Restoration check
Run the same fixtures through both the plugin-root and direct component-directory commands. The malformed files should appear in contents with a YAML parse error and a nonzero exit code. Replacing the malformed frontmatter with valid metadata should produce a per-file result and a successful exit. The malformed-manifest control should continue to fail. These checks preserve component discovery as well as the verdict, so simply returning a failure for every directory would not restore the contract.
Additional context
Preflight completed: the tested artifact is current, and open and closed issues and PRs were searched for this behavior. No exact duplicate appeared in the searched results.
Related: #87004 reports skipped validation for a custom ./.claude/skills manifest path. Its ordinary ./skills control was inspected and failed correctly. The reproduction here uses the conventional plugin skills/ path and direct named component directories; both return zero component results.
Triage assessment
- Impact level: P2
- Assessment status: supported
- Impact basis: Strict validation returns a passing CI verdict for the demonstrated malformed component inputs, with no per-file diagnostics. The observed failure is bounded to this validation workflow; runtime loading and broader platform reach were not tested.
- Workaround status: available
- Workaround basis: Independently parsing these files' YAML frontmatter catches the demonstrated syntax error, as verified with Ruby YAML. That check can safely supplement CI for this syntax defect, although it does not replace Claude Code's other component checks.