After apm install -g --target claude <plugin>, the deployed hook commands in ~/.claude/settings.json reference ${CLAUDE_PLUGIN_ROOT} — which Claude Code only expands inside a plugin's hooks/hooks.json, not inside settings.json (user, project, or local scope). On every matching tool call, Claude Code emits:
PostToolUse:Bash hook error — Hook command references ${CLAUDE_PLUGIN_ROOT} but the hook is not associated with a plugin. This variable is only available in hooks defined in a plugin's hooks/hooks.json file, not in settings.json files.
Net effect: every hook integrated by APM for --target claude is dead config from Claude Code's perspective, and the error message fires repeatedly for every Bash/Edit/Write tool call that matches a hook's if: clause.
This is a regression-class distinct from #1007 (which fixed wrong variable names — ${PLUGIN_ROOT}, ${CURSOR_PLUGIN_ROOT} — being emitted). The variable name written is now correct (${CLAUDE_PLUGIN_ROOT}), but it lives in the wrong file context for Claude Code to expand it.
To Reproduce
apm init
apm marketplace add data-goblin/power-bi-agentic-development (or any marketplace containing a plugin with a hooks/hooks.json that uses ${CLAUDE_PLUGIN_ROOT})
apm install -g -t claude data-goblin/power-bi-agentic-development/plugins/pbi-desktop
cat ~/.claude/settings.json — observe ${CLAUDE_PLUGIN_ROOT} in hook commands inside hooks.PostToolUse/hooks.PreToolUse blocks tagged with _apm_source: pbi-desktop
- Open Claude Code, run any Bash command that matches the hook's
if: clause (e.g. bash <something.ps1> for Bash(* -File *.ps1*))
- Observe the
Hook command references ${CLAUDE_PLUGIN_ROOT} error in the transcript
Expected behavior
After apm install --target claude, hooks deployed for an APM-installed plugin should actually fire when their if: clauses match, without emitting a Claude Code error. Either the variable should be resolvable in the context Claude Code reads it from, or the deployment should write the hooks to a file/location where Claude Code does resolve ${CLAUDE_PLUGIN_ROOT} correctly.
Environment
- OS: Windows 11 Pro 10.0.26200 (the same pattern has been visible on macOS for related issues; not specific to Windows)
- Python Version: bundled with APM 0.13.0 installer
- APM Version: 0.13.0 (latest as of filing)
- Claude Code Version: 4.7 (Opus)
Logs
Excerpt of ~/.claude/settings.json after apm install -g -t claude data-goblin/power-bi-agentic-development/plugins/pbi-desktop then …/pbip:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" refresh-cache",
"timeout": 30,
"if": "Bash(* -File *.ps1*)"
},
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" check-compat",
"timeout": 10,
"if": "Bash(* -File *.ps1*)"
},
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" check-ri",
"timeout": 60,
"if": "Bash(*SaveChanges*)"
}
],
"_apm_source": "pbi-desktop"
},
{
"matcher": "Edit",
"hooks": [
{
"type": "command",
"command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/validate-pbir.sh\"",
"timeout": 10,
"if": "Edit(**.Report/**)"
}
],
"_apm_source": "pbip"
}
]
}
}
Claude Code transcript on any matching Bash call:
PostToolUse:Bash hook error
Failed to run: Hook command references ${CLAUDE_PLUGIN_ROOT} but the hook is not associated with a plugin.
This variable is only available in hooks defined in a plugin's hooks/hooks.json file,
not in settings.json files.
The plugins themselves are well-formed Claude Code plugins (hooks/hooks.json under the plugin root using ${CLAUDE_PLUGIN_ROOT}, per the official Anthropic plugin spec). The bug is purely in APM's emit path for --target claude.
Additional context
Secondary observation — apm uninstall does not clean its own _apm_source blocks
While debugging, ran apm uninstall -g <plugin> then re-checked ~/.claude/settings.json. The hook blocks tagged "_apm_source": "<plugin>" were not removed from the file. The uninstall output reports Cleaned up N integrated hooks but this refers to deployed hook-script files on disk, not to the inlined entries in settings.json. This compounds the impact of the primary bug: even after a clean uninstall, the broken hooks keep firing on every tool call. Reporting here rather than as a separate issue since both behaviours stem from the same --target claude emit/cleanup path.
Related
Why it matters
Any APM-installed plugin that ships a hooks/hooks.json referencing ${CLAUDE_PLUGIN_ROOT} (i.e. any spec-compliant Claude Code plugin authored with the official template) silently emits a Claude Code error on every matching tool call after apm install --target claude. The integration appears successful at install time (N hook(s) integrated -> .claude/settings.json), but no hook ever fires. This affects data-goblin/power-bi-agentic-development (pbi-desktop, pbip) and likely most/all Claude-targeted plugins in the ecosystem.
After
apm install -g --target claude <plugin>, the deployed hook commands in~/.claude/settings.jsonreference${CLAUDE_PLUGIN_ROOT}— which Claude Code only expands inside a plugin'shooks/hooks.json, not insidesettings.json(user, project, or local scope). On every matching tool call, Claude Code emits:Net effect: every hook integrated by APM for
--target claudeis dead config from Claude Code's perspective, and the error message fires repeatedly for every Bash/Edit/Write tool call that matches a hook'sif:clause.This is a regression-class distinct from #1007 (which fixed wrong variable names —
${PLUGIN_ROOT},${CURSOR_PLUGIN_ROOT}— being emitted). The variable name written is now correct (${CLAUDE_PLUGIN_ROOT}), but it lives in the wrong file context for Claude Code to expand it.To Reproduce
apm initapm marketplace add data-goblin/power-bi-agentic-development(or any marketplace containing a plugin with ahooks/hooks.jsonthat uses${CLAUDE_PLUGIN_ROOT})apm install -g -t claude data-goblin/power-bi-agentic-development/plugins/pbi-desktopcat ~/.claude/settings.json— observe${CLAUDE_PLUGIN_ROOT}in hook commands insidehooks.PostToolUse/hooks.PreToolUseblocks tagged with_apm_source: pbi-desktopif:clause (e.g.bash <something.ps1>forBash(* -File *.ps1*))Hook command references ${CLAUDE_PLUGIN_ROOT}error in the transcriptExpected behavior
After
apm install --target claude, hooks deployed for an APM-installed plugin should actually fire when theirif:clauses match, without emitting a Claude Code error. Either the variable should be resolvable in the context Claude Code reads it from, or the deployment should write the hooks to a file/location where Claude Code does resolve${CLAUDE_PLUGIN_ROOT}correctly.Environment
Logs
Excerpt of
~/.claude/settings.jsonafterapm install -g -t claude data-goblin/power-bi-agentic-development/plugins/pbi-desktopthen…/pbip:{ "hooks": { "PostToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" refresh-cache", "timeout": 30, "if": "Bash(* -File *.ps1*)" }, { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" check-compat", "timeout": 10, "if": "Bash(* -File *.ps1*)" }, { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pbi-hooks.sh\" check-ri", "timeout": 60, "if": "Bash(*SaveChanges*)" } ], "_apm_source": "pbi-desktop" }, { "matcher": "Edit", "hooks": [ { "type": "command", "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/validate-pbir.sh\"", "timeout": 10, "if": "Edit(**.Report/**)" } ], "_apm_source": "pbip" } ] } }Claude Code transcript on any matching Bash call:
The plugins themselves are well-formed Claude Code plugins (
hooks/hooks.jsonunder the plugin root using${CLAUDE_PLUGIN_ROOT}, per the official Anthropic plugin spec). The bug is purely in APM's emit path for--target claude.Additional context
Secondary observation —
apm uninstalldoes not clean its own_apm_sourceblocksWhile debugging, ran
apm uninstall -g <plugin>then re-checked~/.claude/settings.json. The hook blocks tagged"_apm_source": "<plugin>"were not removed from the file. The uninstall output reportsCleaned up N integrated hooksbut this refers to deployed hook-script files on disk, not to the inlined entries insettings.json. This compounds the impact of the primary bug: even after a clean uninstall, the broken hooks keep firing on every tool call. Reporting here rather than as a separate issue since both behaviours stem from the same--target claudeemit/cleanup path.Related
.claude/settings.jsonemits Cursor-formatted hooks, bare${PLUGIN_ROOT}, and duplicate entries after fresh install ofmicrosoft/azure-skills#1007 (closed, milestone 0.9.4) — fixed wrong variable names (${PLUGIN_ROOT},${CURSOR_PLUGIN_ROOT}) being emitted into.claude/settings.json. The fix normalized everything to${CLAUDE_PLUGIN_ROOT}— which is the expected variable name for Claude Code plugins — but did not address that this variable only resolves inside a plugin'shooks/hooks.json, not in user/project/localsettings.json. This issue is the next layer down._apm_sourceinto.claude/settings.jsoninvalidates against schema #1279 (open) —_apm_sourceinvalidates the Claude Codesettings.jsonschema. Same code path; may be worth resolving together.Why it matters
Any APM-installed plugin that ships a
hooks/hooks.jsonreferencing${CLAUDE_PLUGIN_ROOT}(i.e. any spec-compliant Claude Code plugin authored with the official template) silently emits a Claude Code error on every matching tool call afterapm install --target claude. The integration appears successful at install time (N hook(s) integrated -> .claude/settings.json), but no hook ever fires. This affectsdata-goblin/power-bi-agentic-development(pbi-desktop, pbip) and likely most/all Claude-targeted plugins in the ecosystem.