[BUG] `claude plugin validate` passes manifests whose hook commands reference files that don't exist
Summary
claude plugin validate does not check that a hook's command resolves to an existing file. A manifest referencing a script that isn't there validates clean and exits 0 — the hook then silently fails at runtime.
Repro
mkdir -p /tmp/vt/p/.claude-plugin /tmp/vt/p/scripts
cat > /tmp/vt/p/.claude-plugin/plugin.json <<'JSON'
{ "name": "vt", "description": "d", "version": "0.1.0",
"hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [
{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/scripts/DOES-NOT-EXIST.sh" } ] } ] } }
JSON
claude plugin validate /tmp/vt/p
/tmp/vt/p/scripts/ is empty. DOES-NOT-EXIST.sh is not there.
Actual
⚠ Found 1 warning:
❯ author: No author information provided. Consider adding author details for plugin attribution
✔ Validation passed with warnings
Exit code 0. The dangling reference is never mentioned — the only warning is about an unrelated missing author field.
Expected
An error, or at minimum a warning, naming the unresolvable path. A hook whose command doesn't exist is not a working plugin, and this is exactly the class of thing a manifest validator is for: the reference is checkable statically, cheaply, at validate time.
Why this matters
The failure is silent in both directions. Validation is green, and at runtime the hook just doesn't fire — no error surfaces to the user, so a plugin whose safety hooks have quietly stopped registering looks identical to one that's working.
I hit this from the adjacent direction: while adding a version field to six plugins, a jq '{name, description, version, author}' template silently dropped the hooks block from four of them — including a permission-gateway plugin's registration of its own PreToolUse gate. claude plugin validate returned ✔ on all six. That specific case isn't a bug (a plugin with no hooks is legitimately valid, and the validator has no baseline to diff against), but chasing it surfaced the case above, which is: the reference is right there in the manifest and is never checked.
I was one green checkmark away from shipping a change that disabled every safety hook in a repo. What caught it was reading the diff by hand.
Scope
Verified against hook command fields. I have not checked whether the same gap applies to other path-bearing fields (mcpServers command/args, lspServers, commands/agents/skills directory overrides) — plausibly the same, but I'm only reporting what I ran.
Possibly related: #66987 (lspServers entries silently dropped — schema-valid, no diagnostic) is the same shape in a different field.
Environment
claude --version: 2.1.210- macOS (darwin 25.5.0)