[BUG] Claude hook bundles drop nested package.json module metadata
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.9k
- Forks
- 365
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 132
Description
Describe the bug
When a Claude hook package places a JavaScript hook and its module metadata in a nested directory, APM deploys the JavaScript file but drops the nested package.json. APM then generates a CommonJS sidecar at the higher hook root, so Node executes the nested .js file with the wrong module semantics.
To Reproduce
Steps to reproduce the behavior:
-
Create a Claude hook package with this layout:
hooks/ hooks.json esm/ entry.js package.json -
Set
hooks/hooks.jsonto:{ "hooks": { "SessionStart": [ { "hooks": [ { "type": "command", "command": "node ./esm/entry.js" } ] } ] } } -
Set
hooks/esm/entry.jsto an ES module, for example:import { writeFileSync } from "node:fs"; writeFileSync(process.argv[2], "ok\\n"); -
Set
hooks/esm/package.jsonto:{ "type": "module" } -
Install the package into a project with Claude hooks enabled by running
apm install. -
Inspect the deployed hook under
.claude/hooks/<package>/hooks/esm/. -
Observe that
entry.jsis present, butesm/package.jsonis missing. The generated.claude/hooks/<package>/hooks/package.jsoncontains{"type": "commonjs"}. -
Run the deployed hook with Node and observe an import syntax error because the file is treated as CommonJS.
Expected behavior
APM should preserve the nested hooks/esm/package.json, or generate equivalent module metadata at the deployed esm boundary. Running the deployed entry.js should use ES module semantics and complete successfully.
Environment (please complete the following information):
- OS: Windows
- Python Version: 3.12.13
- APM Version: 0.30.0
Logs
APM reports no installation error. Executing the deployed JavaScript file produces a Node module syntax error such as:
SyntaxError: Cannot use import statement outside a module
Additional context
src/apm_cli/integration/hook_bundle.py excludes every file named package.json in iter_deployable_hook_bundle_files, regardless of target. Claude passes exclude_json_files=False, but that option only controls the broader .json suffix filter and does not restore the skipped filename.
_hook_module_type searches the hook source root and its ancestors, not nested metadata directories. With no package-root or hooks/package.json, it therefore defaults to CommonJS and writes one sidecar at the deployed hook root.
This is distinct from #2824, which concerns missing .claude-plugin/plugin.json and other files outside the selected hook script bundle. This defect concerns module metadata inside an otherwise selected bundle.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read src/apm_cli/integration/hook_bundle.py, starting with iter_deployable_hook_bundle_files and _hook_module_type, then reproduce the nested hooks/esm layout with apm install. Done means the deployed esm/package.json is preserved or equivalent metadata is placed at the esm boundary, and entry.js runs with ES module semantics.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, python
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100