Skip to content

[BUG] Claude hook bundles drop nested package.json module metadata #2950

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:

  1. Create a Claude hook package with this layout:

    hooks/
      hooks.json
      esm/
        entry.js
        package.json
    
  2. Set hooks/hooks.json to:

    {
      "hooks": {
        "SessionStart": [
          {
            "hooks": [
              {
                "type": "command",
                "command": "node ./esm/entry.js"
              }
            ]
          }
        ]
      }
    }
  3. Set hooks/esm/entry.js to an ES module, for example:

    import { writeFileSync } from "node:fs";
    writeFileSync(process.argv[2], "ok\\n");
  4. Set hooks/esm/package.json to:

    { "type": "module" }
  5. Install the package into a project with Claude hooks enabled by running apm install.

  6. Inspect the deployed hook under .claude/hooks/<package>/hooks/esm/.

  7. Observe that entry.js is present, but esm/package.json is missing. The generated .claude/hooks/<package>/hooks/package.json contains {"type": "commonjs"}.

  8. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage/recommendedAutomated advice completed; not human scope approval.type/bugSomething does not work as documented.

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions