feat(plh): flag plugin-agent frontmatter Claude Code ignores (v5.5.0 / E)

Plugin subagents silently ignore `hooks`/`mcpServers`/`permissionMode`
frontmatter — these are honored only for user/project agents in
.claude/agents/ (code.claude.com/docs sub-agents). Setting them in a
plugin agent is dead config; `permissionMode` is MEDIUM because it
implies a restriction Claude Code does not apply (false security).
hooks/mcpServers are LOW.

Additive to PLH's agent-frontmatter loop (no new scanner, count stays
13). One humanizer pattern covers the three field titles. Hermetic
temp-fixture test (positive + negative). Suite 954 -> 957, byte-stable.

Part of v5.5.0 "steering-model I". Foundation (active-config-reader
enumeration) deferred to v5.6 with B — A/E are additive and don't
consume it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ter3E2JSi1Khgmuf2kady8
This commit is contained in:
Kjell Tore Guttormsen 2026-06-20 11:11:12 +02:00
commit f75ed5655c
4 changed files with 105 additions and 2 deletions

View file

@ -706,6 +706,14 @@ export const TRANSLATIONS = {
},
},
patterns: [
{
regex: /^Plugin agent sets ".+", which Claude Code ignores$/,
translation: {
title: 'A plugin agent sets a field Claude Code ignores',
description: 'Plugin subagents ignore the `hooks`, `mcpServers`, and `permissionMode` settings — only agents in `.claude/agents/` honor them. The field shown here has no effect, and `permissionMode` can give a false sense of restriction.',
recommendation: 'Remove the field, or move the agent into `.claude/agents/`, where it takes effect.',
},
},
{
regex: /^Missing required field in plugin\.json/,
translation: {

View file

@ -47,6 +47,17 @@ const REQUIRED_AGENT_FRONTMATTER = [
{ key: 'description', display: 'description' },
];
// Plugin subagents silently ignore these frontmatter keys — they are honored
// ONLY for user/project agents in .claude/agents/ (code.claude.com/docs
// sub-agents, "ignored for plugin subagents"). Setting them in a plugin agent
// is dead config; permissionMode is MEDIUM because it implies a restriction
// that Claude Code does not actually apply (false sense of security).
const PLUGIN_AGENT_IGNORED_FIELDS = [
{ key: 'permissionMode', severity: SEVERITY.medium },
{ key: 'hooks', severity: SEVERITY.low },
{ key: 'mcpServers', severity: SEVERITY.low },
];
// Component-path keys that REPLACE the default folder (per code.claude.com/docs
// plugins-reference#path-behavior-rules). When such a key is set, Claude Code
// stops scanning the default folder; if that folder still exists, its contents
@ -419,6 +430,23 @@ async function scanSinglePlugin(pluginDir) {
}));
}
}
// Plugin subagents ignore hooks/mcpServers/permissionMode frontmatter (V15)
// — dead config (permissionMode = medium: false sense of restriction).
for (const { key, severity } of PLUGIN_AGENT_IGNORED_FIELDS) {
if (frontmatter[key] !== undefined) {
findings.push(finding({
scanner: SCANNER,
severity,
title: `Plugin agent sets "${key}", which Claude Code ignores`,
description: `Agent "${file}" in plugin "${pluginName}" sets "${key}" in frontmatter, but Claude Code ignores ${key} for plugin subagents — ${key === 'permissionMode' ? 'the agent runs with default permissions, not the restricted mode this implies' : 'this configuration has no effect'}.`,
file: filePath,
evidence: `${key}: ${JSON.stringify(frontmatter[key])}`,
recommendation: `Remove "${key}" from the agent frontmatter, or ship the agent as a user/project agent in .claude/agents/, where ${key} is honored.`,
autoFixable: false,
}));
}
}
}
} catch { /* no agents dir */ }