Summary
Three plugin-specific divergences from the documented Claude Code plugin format: generated plugin hooks reference $CLAUDE_PROJECT_DIR instead of ${CLAUDE_PLUGIN_ROOT} (so bundled hook scripts never resolve for plugin consumers), plugin-shipped subagents emit frontmatter fields upstream forbids for security (permissionMode, hooks, mcpServers), and plugin.json component-path overrides for commands/agents are ignored, so generated files can land in directories Claude Code will not scan.
Upstream baseline: Claude Code v2.1.220 (CHANGELOG). The last ~3 months of plugin-related changelog entries are bug fixes (v2.1.216 plugin-skill name prefix, v2.1.210 plugin MCP teardown, v2.1.206/v2.1.217 plugin LSP handling) — these are standing divergences from the current documented format rather than a fresh release rulesync missed.
Recent Releases
- v2.1.219 — agent names containing
: are rejected (reserved for plugin namespacing) — CHANGELOG.
- v2.1.140+ —
claude plugin list warns when a manifest path override causes a default component directory to be ignored — plugins reference.
Gaps
1. hooks — plugin hook commands are rewritten with $CLAUDE_PROJECT_DIR instead of ${CLAUDE_PLUGIN_ROOT}
- Upstream: plugin hook commands resolve against the plugin's own install directory, verbatim example:
"command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/format-code.sh" — plugins reference, Hooks section. ${CLAUDE_PLUGIN_ROOT} is the plugin install dir; ${CLAUDE_PROJECT_DIR} is the consumer's project root.
- rulesync (
project): src/features/hooks/claudecode-plugin-hooks.ts only overrides getSettablePaths() and inherits the converter config from ClaudecodeHooks, including projectDirVar: "$CLAUDE_PROJECT_DIR" (src/features/hooks/claudecode-hooks.ts). Verified end to end: a canonical hook with "command": "./scripts/fmt.sh" generated into a plugin root produces "command": "\"$CLAUDE_PROJECT_DIR\"/scripts/fmt.sh". The script ships inside the plugin, so for every consumer this expands to a nonexistent path in their repo and the hook silently fails. ${CLAUDE_PLUGIN_ROOT} appears nowhere in src/.
2. subagents — plugin-shipped agents emit frontmatter upstream forbids for security
- Upstream, verbatim: "Plugin agents support
name, description, model, effort, maxTurns, tools, disallowedTools, skills, memory, background, and isolation frontmatter fields. The only valid isolation value is \"worktree\". For security reasons, hooks, mcpServers, and permissionMode are not supported for plugin-shipped agents." — plugins reference, Agents section.
- rulesync (
project): ClaudecodePluginSubagent inherits fromRulesyncSubagent from ClaudecodeSubagent, whose schema permits permissionMode, hooks, and mcpServers (src/features/subagents/claudecode-subagent.ts). A subagent with claudecode: { permissionMode: acceptEdits, mcpServers: {} } generated into a plugin root emits exactly that frontmatter into agents/*.md. Upstream discards these fields, so the author believes the plugin agent is permission-constrained when it is not — a misleading security posture, not just dead metadata. isolation is also unvalidated against the plugin-only "worktree" restriction.
3. commands / subagents — plugin.json component-path overrides are ignored
- Upstream path behavior rules, verbatim: "Replaces the default:
commands, agents, workflows, outputStyles, experimental.themes, experimental.monitors. For example, when the manifest specifies commands, the default commands/ directory is not scanned." (skills adds to the default scan; hooks/mcpServers/lspServers merge, so only commands and agents are affected.) — plugins reference, Path behavior rules.
- rulesync (
project): output directories are hardcoded (CLAUDECODE_PLUGIN_COMMANDS_DIR = "commands", CLAUDECODE_PLUGIN_AGENTS_DIR = "agents" in src/constants/plugin-paths.ts) and rulesync never reads the user's existing .claude-plugin/plugin.json. Generating into a plugin whose manifest declares {"agents": "./custom/agents/"} still writes agents/*.md, which Claude Code will not scan.
Proposed Follow-up
- In
ClaudecodePluginHooks, override the converter config with projectDirVar: "$CLAUDE_PLUGIN_ROOT" (keeping prefixDotRelativeCommandsOnly: true), and teach the import direction to recognize ${CLAUDE_PLUGIN_ROOT} so plugin hooks round-trip. Consider the same substitution for plugin MCP server command values, currently passed through verbatim.
- In
ClaudecodePluginSubagent, strip hooks, mcpServers, and permissionMode on generate (with a warning), constrain isolation to "worktree", and validate that agent names contain no : (v2.1.219).
- When
.claude-plugin/plugin.json exists, honor a single-directory commands/agents override as the output root — or at minimum warn loudly when a replacing override points somewhere rulesync is not writing. Import should follow the same paths.
Non-gaps (verified)
- Hook event coverage is current —
DirectoryAdded and InstructionsLoaded are both in src/types/hooks.ts.
rules and permissions are correctly unsupported: upstream states a plugin-root CLAUDE.md is not loaded as project context, and plugin settings.json accepts only agent and subagentStatusLine.
- Newer plugin surfaces (
outputStyles, experimental.themes/monitors, lspServers, bin/, userConfig, channels, dependencies, marketplace.json) have no corresponding rulesync feature dimension — following them would be new features, not divergence fixes.
References
Summary
Three plugin-specific divergences from the documented Claude Code plugin format: generated plugin hooks reference
$CLAUDE_PROJECT_DIRinstead of${CLAUDE_PLUGIN_ROOT}(so bundled hook scripts never resolve for plugin consumers), plugin-shipped subagents emit frontmatter fields upstream forbids for security (permissionMode,hooks,mcpServers), andplugin.jsoncomponent-path overrides forcommands/agentsare ignored, so generated files can land in directories Claude Code will not scan.Upstream baseline: Claude Code v2.1.220 (CHANGELOG). The last ~3 months of plugin-related changelog entries are bug fixes (v2.1.216 plugin-skill
nameprefix, v2.1.210 plugin MCP teardown, v2.1.206/v2.1.217 plugin LSP handling) — these are standing divergences from the current documented format rather than a fresh release rulesync missed.Recent Releases
:are rejected (reserved for plugin namespacing) — CHANGELOG.claude plugin listwarns when a manifest path override causes a default component directory to be ignored — plugins reference.Gaps
1. hooks — plugin hook commands are rewritten with
$CLAUDE_PROJECT_DIRinstead of${CLAUDE_PLUGIN_ROOT}"command": "\"${CLAUDE_PLUGIN_ROOT}\"/scripts/format-code.sh"— plugins reference, Hooks section.${CLAUDE_PLUGIN_ROOT}is the plugin install dir;${CLAUDE_PROJECT_DIR}is the consumer's project root.project):src/features/hooks/claudecode-plugin-hooks.tsonly overridesgetSettablePaths()and inherits the converter config fromClaudecodeHooks, includingprojectDirVar: "$CLAUDE_PROJECT_DIR"(src/features/hooks/claudecode-hooks.ts). Verified end to end: a canonical hook with"command": "./scripts/fmt.sh"generated into a plugin root produces"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/fmt.sh". The script ships inside the plugin, so for every consumer this expands to a nonexistent path in their repo and the hook silently fails.${CLAUDE_PLUGIN_ROOT}appears nowhere insrc/.2. subagents — plugin-shipped agents emit frontmatter upstream forbids for security
name,description,model,effort,maxTurns,tools,disallowedTools,skills,memory,background, andisolationfrontmatter fields. The only validisolationvalue is\"worktree\". For security reasons,hooks,mcpServers, andpermissionModeare not supported for plugin-shipped agents." — plugins reference, Agents section.project):ClaudecodePluginSubagentinheritsfromRulesyncSubagentfromClaudecodeSubagent, whose schema permitspermissionMode,hooks, andmcpServers(src/features/subagents/claudecode-subagent.ts). A subagent withclaudecode: { permissionMode: acceptEdits, mcpServers: {} }generated into a plugin root emits exactly that frontmatter intoagents/*.md. Upstream discards these fields, so the author believes the plugin agent is permission-constrained when it is not — a misleading security posture, not just dead metadata.isolationis also unvalidated against the plugin-only"worktree"restriction.3. commands / subagents —
plugin.jsoncomponent-path overrides are ignoredcommands,agents,workflows,outputStyles,experimental.themes,experimental.monitors. For example, when the manifest specifiescommands, the defaultcommands/directory is not scanned." (skillsadds to the default scan;hooks/mcpServers/lspServersmerge, so onlycommandsandagentsare affected.) — plugins reference, Path behavior rules.project): output directories are hardcoded (CLAUDECODE_PLUGIN_COMMANDS_DIR = "commands",CLAUDECODE_PLUGIN_AGENTS_DIR = "agents"insrc/constants/plugin-paths.ts) and rulesync never reads the user's existing.claude-plugin/plugin.json. Generating into a plugin whose manifest declares{"agents": "./custom/agents/"}still writesagents/*.md, which Claude Code will not scan.Proposed Follow-up
ClaudecodePluginHooks, override the converter config withprojectDirVar: "$CLAUDE_PLUGIN_ROOT"(keepingprefixDotRelativeCommandsOnly: true), and teach the import direction to recognize${CLAUDE_PLUGIN_ROOT}so plugin hooks round-trip. Consider the same substitution for plugin MCP servercommandvalues, currently passed through verbatim.ClaudecodePluginSubagent, striphooks,mcpServers, andpermissionModeon generate (with a warning), constrainisolationto"worktree", and validate that agent names contain no:(v2.1.219)..claude-plugin/plugin.jsonexists, honor a single-directorycommands/agentsoverride as the output root — or at minimum warn loudly when a replacing override points somewhere rulesync is not writing. Import should follow the same paths.Non-gaps (verified)
DirectoryAddedandInstructionsLoadedare both insrc/types/hooks.ts.rulesandpermissionsare correctlyunsupported: upstream states a plugin-rootCLAUDE.mdis not loaded as project context, and pluginsettings.jsonaccepts onlyagentandsubagentStatusLine.outputStyles,experimental.themes/monitors,lspServers,bin/,userConfig,channels,dependencies,marketplace.json) have no corresponding rulesync feature dimension — following them would be new features, not divergence fixes.References
${CLAUDE_PLUGIN_ROOT}hook commands, restricted agent frontmatter set, path behavior rules (all three gaps).:-in-agent-name rejection; recent plugin fixes confirming no format changes in the window.