Skip to content

[BUG] User-scope hook deployment leaves out the plugin manifest: scripts reading ../../.claude-plugin/plugin.json crash (Codex Companion plugin) #2824

Open

Description

@lkshrk

Summary

Follow-up to #2023. Hook deployment now copies the sibling modules next to a hook entry script, but a plugin whose hook scripts read files outside the scripts directory still crashes at runtime, because ${CLAUDE_PLUGIN_ROOT} is rewritten to the partial copy under ~/.claude/hooks/<pkg>/ rather than to the full package tree.

Concrete case: openai/codex-plugin-cc/plugins/codex (Codex Companion). hooks/hooks.json declares

"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop-review-gate-hook.mjs\""

and scripts/lib/app-server.mjs does

const PLUGIN_MANIFEST_URL = new URL("../../.claude-plugin/plugin.json", import.meta.url);
const PLUGIN_MANIFEST = JSON.parse(fs.readFileSync(PLUGIN_MANIFEST_URL, "utf8"));

After apm install -g the deployed tree is ~/.claude/hooks/codex/scripts/{lib/,*.mjs,package.json} only. Every Stop / SessionStart / SessionEnd invocation then fails:

Error: ENOENT: no such file or directory, open '/Users/<me>/.claude/hooks/codex/.claude-plugin/plugin.json'
    at file:///Users/<me>/.claude/hooks/codex/scripts/lib/app-server.mjs:20:39

The same plugin installed natively through Claude Code's plugin system works, because there ${CLAUDE_PLUGIN_ROOT} is the whole plugin directory. The plugin also ships prompts/ and schemas/ next to scripts/, which the review gate reads through the broker.

Expected

Either of:

  1. Rewrite ${CLAUDE_PLUGIN_ROOT} (and ${PLUGIN_ROOT}) to the package's own directory under apm_modules/<owner>/<pkg>/… for user-scope deploys instead of copying scripts into ~/.claude/hooks/<pkg>/. The full tree already exists there, so every relative read works and nothing is duplicated.
  2. If copying stays, copy the plugin manifest (.claude-plugin/plugin.json) and the package's non-primitive directories alongside the hook scripts, so the deployed root is a faithful plugin root.

Environment

  • apm 0.29.0 from commit 656f3d6e6dbade369b6fc8727a4eb7b164409b6d
  • macOS 15 arm64, target claude, user scope
  • Workaround in use: copying .claude-plugin/plugin.json into ~/.claude/hooks/codex/.claude-plugin/ by hand

Activity

  1. added 2 commits that reference this issue on Sep 5, 2026
    8046e2b
    6f96c1f
  2. added a commit that references this issue on Sep 6, 2026
    6cbb0a9
  3. github-actions commented on Sep 9, 2026

    @github-actions

    Triage decision

    accept

    Proposed labels

    theme/portability
    area/multi-target
    type/bug
    status/accepted
    

    Milestone

    null -- open-milestone read failed (401 from milestones API); no milestone assigned this run.

    Suggested next action

    Rewrite ${CLAUDE_PLUGIN_ROOT} to the package's own directory under apm_modules for user-scope deploys (option 1 in the report), or copy the plugin manifest and non-primitive directories alongside hook scripts (option 2), so relative reads outside the scripts directory resolve correctly.

    Suggested issue comment

    Good concrete repro (Codex Companion plugin) showing the partial-copy deploy root breaks any hook script that reads outside its own scripts/ directory. Accepting as a bug -- either of your two proposed directions closes the gap; a maintainer will pick between rewriting ${CLAUDE_PLUGIN_ROOT} to the full package tree vs. copying the manifest and sibling directories. No milestone assigned yet (milestone read failed this run).

    Per-lens notes (collapsed)

    DevX UX Expert -- User-Need Reviewer

    A plugin that works when installed natively through Claude Code's own plugin system but crashes at runtime through APM's deployment is a real portability break for exactly the kind of third-party hook plugins APM aims to support.

    Supply Chain Security Expert -- Risk-Surface Reviewer

    No risk-surface implication; this is a deployed-root completeness bug, not a trust or containment change.

    OSS Growth Hacker -- Contributor-Tone Reviewer

    Not activated -- lkshrk has 10 prior issues on this repo, well past the first-time-contributor threshold.

    Python Architect -- Architecture Reviewer

    Not activated -- both proposed fixes are localized to the hook-deployment copy path (either the rewrite target or what gets copied); no new cross-cutting interface is required, though a maintainer should pick one of the two options rather than doing both.

    Doc Writer -- Documentation Reviewer

    Not activated -- no new user-facing documentation surface implied beyond existing hook-deployment behavior.

    APM CEO -- Triage Arbiter

    Concrete, reproducible, follow-up to a known area (#2023). Accept as type/bug, theme/portability, area/multi-target; implementation direction left to maintainer choice between the two proposed options.

    {
      "decision": "accept",
      "decision_detail": "",
      "theme": "theme/portability",
      "areas": ["area/multi-target"],
      "type": "type/bug",
      "status": "status/accepted",
      "priority": null,
      "preserved_labels": [],
      "milestone": null,
      "next_action": "Rewrite ${CLAUDE_PLUGIN_ROOT} to the package's own directory under apm_modules for user-scope deploys (option 1 in the report), or copy the plugin manifest and non-primitive directories alongside hook scripts (option 2), so relative reads outside the scripts directory resolve correctly.",
      "comment_markdown": "Good concrete repro (Codex Companion plugin) showing the partial-copy deploy root breaks any hook script that reads outside its own scripts/ directory. Accepting as a bug -- either of your two proposed directions closes the gap; a maintainer will pick between rewriting ${CLAUDE_PLUGIN_ROOT} to the full package tree vs. copying the manifest and sibling directories. No milestone assigned yet (milestone read failed this run)."
    }

    Triage status: agentic proposal pending human ratification.
    Silence is approval. Maintainers can:

    • Override any label or milestone above by editing it directly --
      human edits are authoritative and will not be reverted on
      subsequent runs.
    • Re-trigger triage by applying the status/needs-triage label, or
      by removing status/triaged to enroll the issue in the next
      daily sweep.

    Posted by the Triage Panel workflow. See packages/apm-triage-panel for the panel skill.

    Generated by Triage Panel · copilot · auto · 165.9 AIC · ⌖ 21.2 AIC · ⊞ 14.1K · ◷

  4. added
    theme/portabilityOne manifest, every target. Multi-target deploy, marketplace, packaging, install.
    area/multi-targetMulti-target deploy spec, target directory creation, agent surface routing.
    type/bugSomething does not work as documented.
    status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.
    status/triagedAutomated advice completed; deduplication only. Not human approval; silence is not approval.
    on Sep 9, 2026
  5. github-actions commented on Sep 10, 2026

    @github-actions

    Triage decision

    accept

    Proposed labels

    theme/portability
    area/multi-target
    type/bug
    status/accepted
    

    Milestone

    null

    Suggested next action

    Fix ${CLAUDE_PLUGIN_ROOT} rewriting so it resolves to the full deployed plugin tree (not just the partial hook-scripts copy under ~/.claude/hooks/<name>/), so sibling files outside the hook scripts directory (e.g. .claude-plugin/plugin.json, prompts/, schemas/) remain reachable at runtime.

    Suggested issue comment

    Confirmed as a follow-up to #2023 -- hook deployment now copies sibling script modules, but `${CLAUDE_PLUGIN_ROOT}` still resolves to the partial copy under `~/.claude/hooks/<name>/` rather than the full plugin tree, so any script reading files outside the scripts directory (like `.claude-plugin/plugin.json`) crashes at runtime. This is accepted; the fix should make the deployed layout (or the rewritten root variable) cover the whole plugin tree that native Claude Code plugin installs expose.

    Per-lens notes (collapsed)

    DevX UX Expert -- User-Need Reviewer

    Concrete runtime crash for a real-world plugin (Codex Companion) with an exact stack trace and file layout diff versus native install. This is a genuine parity gap between APM-deployed and natively-installed Claude Code plugins.

    Supply Chain Security Expert -- Risk-Surface Reviewer

    No new risk-surface implication -- deployed content is the same, just incompletely reachable at runtime. theme/portability remains primary; no theme/security needed.

    OSS Growth Hacker -- Contributor-Tone Reviewer

    Not activated -- author_association is CONTRIBUTOR.

    Python Architect -- Architecture Reviewer

    Not activated -- this is a bounded fix to the hook-deployment copy scope / path-rewriting logic, building directly on the existing #2023 mechanism rather than introducing a new architecture.

    Doc Writer -- Documentation Reviewer

    Not activated -- internal deployment-completeness fix; no new doc surface implied.

    APM CEO -- Triage Arbiter

    Clear, well-scoped follow-up bug to a previously accepted fix (#2023), with a concrete real-world plugin repro. Accepting as theme/portability, area/multi-target, type/bug.

    {
      "decision": "accept",
      "decision_detail": "",
      "theme": "theme/portability",
      "areas": ["area/multi-target"],
      "type": "type/bug",
      "status": "status/accepted",
      "priority": null,
      "preserved_labels": [],
      "milestone": null,
      "next_action": "Fix ${CLAUDE_PLUGIN_ROOT} rewriting so it resolves to the full deployed plugin tree (not just the partial hook-scripts copy), so sibling files outside the hook scripts directory remain reachable at runtime.",
      "comment_markdown": "Confirmed as a follow-up to #2023 -- hook deployment now copies sibling script modules, but ${CLAUDE_PLUGIN_ROOT} still resolves to the partial copy under ~/.claude/hooks/<name>/ rather than the full plugin tree, so any script reading files outside the scripts directory (like .claude-plugin/plugin.json) crashes at runtime. This is accepted; the fix should make the deployed layout (or the rewritten root variable) cover the whole plugin tree that native Claude Code plugin installs expose."
    }

    Triage status: agentic proposal pending human ratification.
    Silence is approval. Maintainers can:

    • Override any label or milestone above by editing it directly -- human edits are authoritative and will not be reverted on subsequent runs.
    • Re-trigger triage by applying the status/needs-triage label, or by removing status/triaged to enroll the issue in the next daily sweep.

    Posted by the Triage Panel workflow. See packages/apm-triage-panel for the panel skill.

    Generated by Triage Panel · copilot · auto · 76.9 AIC · ⌖ 22.5 AIC · ⊞ 14.1K · ◷

  6. danielmeppiel commented on Sep 12, 2026

    @danielmeppiel
    Collaborator

    At the lead maintainer's direction, the current acceptance of this item is withdrawn as part of APM's roadmap and contribution reset. Previous acceptance labels or comments must not be treated as approval to start or continue substantive implementation for inclusion in APM.

    Thank you for the work and ideas already contributed. Our small maintainer team needs to agree supported scope and review capacity before inviting more implementation. This is not a rejection of the proposal or a judgment of your work, and it does not undo completed work or erase its history.

    Please follow the contribution rules: a responsible human maintainer needs to record the bounded scope, acceptance criteria and review contact on the issue before substantive work resumes. Existing issue discussion, assignments and contributions remain available for that decision. Trivial typo and broken-link corrections retain their standing preapproval.

  7. added
    triage/recommendedAutomated advice completed; not human scope approval.
    and removed
    status/triagedAutomated advice completed; deduplication only. Not human approval; silence is not approval.
    on Sep 12, 2026
  8. lkshrk commented on Sep 19, 2026

    @lkshrk
    ContributorAuthor

    #2826 is the manifest-only slice of this issue and is now rewritten to the current PR template. The 2026-09-12 withdrawal asked for a recorded bounded scope before substantive work resumes; status/accepted was re-applied on 2026-09-18, but there is no apm-scope:v1 record yet.

    When a maintainer has capacity, please record whether the manifest-only slice (.claude-plugin/plugin.json next to the deployed hook scripts, prompts/ and schemas/ remaining here) is the accepted bound, or whether the full plugin-root layout is wanted instead, plus a review contact. I will narrow, widen, or close #2826 to match.

  9. added a commit that references this issue on Sep 19, 2026
    462f2c5
  10. festo commented on Sep 28, 2026

    @festo

    Another reproduction of this, with a widely used package, wher manifest-only fix would not help.

    Setup (APM 0.31.0 8fd10ac, Linux):

    targets:

    • claude
    • codex

    dependencies:
    apm:
    - obra/superpowers

    obra/superpowers' hooks/session-start resolves the plugin root as the parent of its own directory and reads a skill from there:

    PLUGIN_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
    cat "${PLUGIN_ROOT}/skills/using-superpowers/SKILL.md"
    

    After apm install, the hook bundle holds only the scripts. The skills are deployed separately, to each target's skills directory (.claude/skills/, .agents/skills/):

    .claude/hooks/superpowers/hooks/{run-hook.cmd,session-start}
    .codex/hooks/superpowers/hooks/{run-hook.cmd,session-start}
    

    So on both targets, every SessionStart hook injects an error where the skill content should be:

    cat: <repo>/.codex/hooks/superpowers/skills/using-superpowers/SKILL.md: No such file or directory
    Error reading using-superpowers skill
    

    The command exits 0, so no harness shows an error.

  11. danielmeppiel commented on Sep 28, 2026

    @danielmeppiel
    Collaborator

    Decision: withdraw
    Area: project
    Reason: The manifest-only/partial plugin-projection implementation direction is superseded by native-plugin epic #3094, planned for Next. Issue #2824 remains open as a regression sub-issue; its Codex Companion and Superpowers reproductions are acceptance cases, not resolved bugs. PR #2826 will close unmerged because the maintainer has changed the installation architecture, not because the contributor's work was rejected on quality grounds. Thank you to lkshrk (@lkshrk) and Gergely (@festo) for the evidence. Do not start or revive the old implementation from previous comments or status/accepted; a native replacement needs separately recorded bounded scope, acceptance criteria and a responsible review contact. This withdrawal does not remove support for standalone primitives or grant implementation permission for the epic.

  12. added
    status/deferredNot invited for implementation now; no release commitment.
    status/needs-designDesign discussion required before implementation; not scope approval by itself.
    status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.
    and removed
    status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.
    status/deferredNot invited for implementation now; no release commitment.
    on Sep 28, 2026
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

    area/multi-targetMulti-target deploy spec, target directory creation, agent surface routing.status/acceptedHuman scope approval; verify the issue's approval record and review contact before work.status/needs-designDesign discussion required before implementation; not scope approval by itself.theme/portabilityOne manifest, every target. Multi-target deploy, marketplace, packaging, install.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