Describe the bug
After apm install -g, hooks declared in a package's .apm/hooks/ don't fire on any of the three hook-capable targets. The install log lies in every case: it reports an "integrated" count for writes that did not happen (claude, cursor) or that wrote unusable content (copilot). Users get no signal of failure.
| Target |
Log says |
Actual |
| claude |
1 hook(s) integrated -> .claude/settings.json |
~/.claude/settings.json not modified |
| cursor |
1 hook(s) integrated -> .cursor/hooks.json |
~/.cursor/hooks.json not modified |
| copilot |
1 hook(s) integrated -> .copilot/hooks/ |
File written, but Stop event not renamed to session.shutdown and ${PLUGIN_ROOT} left unexpanded |
The canonical source ~/.apm/apm_modules/<pkg>/.apm/hooks/<file>.json is present after install but never merged into the user-scope harness config.
To Reproduce
Package with .apm/hooks/session-metrics.json:
{ "Stop": [ { "matcher": "", "hooks": [
{ "type": "command",
"command": "python3 ${PLUGIN_ROOT}/scripts/example.py",
"timeout": 20000 } ] } ] }
cd ~/.apm
apm install -g --target claude --force
apm install -g --target cursor --force
apm install -g --target copilot --force
cat ~/.claude/settings.json # hooks: {}
cat ~/.cursor/hooks.json # hooks: {}
cat ~/.copilot/hooks/*.json # raw Claude payload, ${PLUGIN_ROOT} unresolved
Reproduces identically with a GitHub-fetched branch dep (owner/repo/path#branch); not specific to local paths.
Also reproduces with the --runtime <name> form, with a different log shape:
$ apm install -g <pkg> --runtime claude --force
...
|-- 3 hook(s) integrated -> 3 targets
i.e. asking for runtime claude still produces a -> 3 targets summary, and file inspection confirms cursor/copilot were also touched (or attempted).
Expected
- claude/cursor: merge into user-scope
settings.json / hooks.json
- copilot: rename event per-target (Copilot hooks reference) and expand
${PLUGIN_ROOT} before writing
i.e. what project-scope already produces, applied to target.root_dir at user scope. The install log should accurately reflect what was written: no "integrated" claim for a file that wasn't modified, a count that reflects declared hooks rather than per-target deliveries, and --runtime <name> should restrict deployment to the named runtime.
Environment
APM 0.15.0, macOS, Claude Code 4.7, Cursor recent, Copilot CLI >= 1.0.44.
Additional notes
- Install logs lie: report success on writes that didn't happen (claude, cursor) or wrote unusable content (copilot). This is the most impactful part of the bug because it eliminates the signal a user would otherwise use to debug.
--runtime claude doesn't filter; log shows -> 3 targets and cursor/copilot destination files are still touched.
N hook(s) integrated counts per-target deliveries, not declared hooks. One declared hook is reported as 1 -> <path> per --target X run, or 3 -> 3 targets from a single --runtime X run.
Related
Describe the bug
After
apm install -g, hooks declared in a package's.apm/hooks/don't fire on any of the three hook-capable targets. The install log lies in every case: it reports an "integrated" count for writes that did not happen (claude, cursor) or that wrote unusable content (copilot). Users get no signal of failure.1 hook(s) integrated -> .claude/settings.json~/.claude/settings.jsonnot modified1 hook(s) integrated -> .cursor/hooks.json~/.cursor/hooks.jsonnot modified1 hook(s) integrated -> .copilot/hooks/Stopevent not renamed tosession.shutdownand${PLUGIN_ROOT}left unexpandedThe canonical source
~/.apm/apm_modules/<pkg>/.apm/hooks/<file>.jsonis present after install but never merged into the user-scope harness config.To Reproduce
Package with
.apm/hooks/session-metrics.json:{ "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "python3 ${PLUGIN_ROOT}/scripts/example.py", "timeout": 20000 } ] } ] }Reproduces identically with a GitHub-fetched branch dep (
owner/repo/path#branch); not specific to local paths.Also reproduces with the
--runtime <name>form, with a different log shape:i.e. asking for runtime
claudestill produces a-> 3 targetssummary, and file inspection confirms cursor/copilot were also touched (or attempted).Expected
settings.json/hooks.json${PLUGIN_ROOT}before writingi.e. what project-scope already produces, applied to
target.root_dirat user scope. The install log should accurately reflect what was written: no "integrated" claim for a file that wasn't modified, a count that reflects declared hooks rather than per-target deliveries, and--runtime <name>should restrict deployment to the named runtime.Environment
APM 0.15.0, macOS, Claude Code 4.7, Cursor recent, Copilot CLI >= 1.0.44.
Additional notes
--runtime claudedoesn't filter; log shows-> 3 targetsand cursor/copilot destination files are still touched.N hook(s) integratedcounts per-target deliveries, not declared hooks. One declared hook is reported as1 -> <path>per--target Xrun, or3 -> 3 targetsfrom a single--runtime Xrun.Related
${CLAUDE_PLUGIN_ROOT}(the variable-resolution half is still visible for copilot above)~/.copilot/hooks/), noted for completeness