CC ignores three keys in plugin-agent frontmatter
Not inferred from behaviour. Read out of the binary at a known offset, and cross-checked on a second version.
2.1.227 binary, offset 91099675 (identical on 2.1.226)
[permissionMode, hooks, mcpServers]
`Plugin agent file ${e} sets ${G}, which is ignored for
plugin agents. Use .claude/agents/ for this level of
configuration.`
ork was setting all three: permissionMode on 5 agents, hooks on 33, mcpServers on 36. Each was a load-time warning for a setting that does nothing.
The corroborating telemetry
A declaration that reads like a Bash allowlist, on 33 agents, that had never run once. That is the shape of the whole finding.
All 14 referenced hooks stay live
The frontmatter block named 14 distinct hooks. Deleting it removes a redundant second declaration, not the wiring.
| Hook | Real invocation path |
|---|---|
pretool/bash/git-validator | live sync-bash-dispatcher.ts:87 + entries/pretool.ts:96 |
agent/restrict-bash | live sync-bash-dispatcher.ts:26 + entries/agent.ts:28 |
pretool/bash/dangerous-command-blocker | live dispatcher + entries map |
| …and 11 more | live same pattern: dispatcher plus the entries/ record |
The registration contract is hooks.json + entries/<event>.ts as a pair, with related hooks consolidated behind a dispatcher. Agent frontmatter was never part of that contract for plugin agents.
The key is not broken, it was declared at the wrong level
Easy to over-read this change. The scope is precise.
Where hooks: lives | Works? | Meaning |
|---|---|---|
| ork's shipped plugin agents | inert | CC's plugin-agent loader ignores it and warns. Removed here. |
a consumer's .claude/agents/ | works | Exactly as documented. The agent-hooks skill and its eval stay correct and were left alone. |
216 → 171
The advertised hook count drops by 45. That is honest arithmetic, not lost behaviour.
before GLOBAL=150 AGENT=45 SKILL=21 TOTAL=216
after GLOBAL=150 AGENT=0 SKILL=21 TOTAL=171
^^^^^^^
declarations CC never read
Re-stamped across the manifest, plugin.json, hooks.json, README, CONTRIBUTING, marketplace.json and the docs site. bin/validate-counts.sh passes.
A latent bug this exposed
bin/count-hooks.sh could not survive a zero count.
set -euo pipefail
AGENT=$(grep -rch 'command:.*run-hook' src/agents/ | awk '…')
^ exits 1 on no-match
pipefail propagates it
set -e kills the script BEFORE the final echo
→ TOTAL unbound in every caller
Symptom was stamp-counts.sh: line 31: TOTAL: unbound variable, which reads like a stamping bug and is actually a counting bug that only appears when a bucket legitimately reaches zero.
Fixed by inspecting grep's three-valued exit rather than blanket || true: 1 counts as 0, ≥2 still fails loudly with grep's own stderr. A blanket suppression would have turned a broken grep into a silent zero and quietly shrunk the advertised count.
mcpServers: — 36 agents, needs an owner decision
| Fact | Detail |
|---|---|
| CC reads it? | no same ignored-key array as hooks |
| Anything else reads it? | yes tests/performance/test-mcp-overhead.sh asserts “Every agent .md file has an explicit mcpServers field” |
| So the test… | enforces a declaration with no runtime effect for plugin agents. Deleting the field breaks the test; keeping both preserves a guard over a no-op. |
Two coherent options: (a) delete the field and retire or rewrite the test to check something CC actually honours, or (b) keep both and document that the field is advisory metadata rather than configuration. That is a product call, so it is surfaced rather than taken.