Skills declaring allowed-tools silently fail to load under headless `claude -p`
Summary
A skill that declares allowed-tools in its frontmatter silently fails to load under headless claude -p. The Skill tool returns is_error: true with no message, and the session then proceeds without the skill content while reporting overall success. The same skill loads correctly in an interactive session.
Because the run "succeeds," this is invisible to anything that only checks exit status — including evals and CI. It cost us a real eval: a docs-review skill scored identical to a no-plugin baseline, and the cause turned out to be that the skill had never loaded at all.
Environment
- Claude Code 2.1.208, macOS
Reproduction
Minimal plugin with one skill. The only difference between variants is a single frontmatter line.
---
name: probe
description: Probe skill. Use when the user says probe.
allowed-tools:
- Read
---
Reply with exactly the token GRONK_LOADED and nothing else.
claude -p "Invoke the probe skill. Then say one word." \
--plugin-dir ./probe-plugin --max-turns 4 --output-format stream-json --verbose
Results
| skill frontmatter | permission mode | Skill tool result |
|---|---|---|
| has allowed-tools | default | {"content":"Execute skill: probe:probe","is_error":true} |
| has allowed-tools | --permission-mode acceptEdits | is_error: true |
| has allowed-tools | --dangerously-skip-permissions | {"content":"Launching skill: probe:probe"} — loads |
| no allowed-tools (control) | default | {"content":"Launching skill: probe:probe"} — loads |
Interactive session, default permission mode, same skill with allowed-tools: loads normally.
So the field is fine — but the permission grant it requests cannot be satisfied in -p, where there is no trust dialog or prompt, and the failure is hard rather than graceful.
Expected
One of:
-pdegrades gracefully — load the skill, and simply don't pre-grant theallowed-toolspermissions (fall back to the session's normal permission behavior); or- it fails loudly — a real error message naming
allowed-toolsand the unsatisfiable grant, rather than a bareis_errorwith the session continuing as if nothing happened.
The current behavior is the worst of both: the skill is silently dropped and the run reports success.
Note on the validators
claude plugin validate --strict passes the field, and it is fully documented in the skills reference (frontmatter table; four worked examples use it). So there is no static signal that a skill will not load in this mode.