Repository navigation
[BUG] User-scope hook deployment leaves out the plugin manifest: scripts reading ../../.claude-plugin/plugin.json crash (Codex Companion plugin) #2824
Description
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:
- Rewrite
${CLAUDE_PLUGIN_ROOT}(and${PLUGIN_ROOT}) to the package's own directory underapm_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. - 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.jsoninto~/.claude/hooks/codex/.claude-plugin/by hand
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
fix(hooks): deploy the plugin manifest next to user-scope hook scripts
fix(hooks): deploy the plugin manifest next to user-scope hook scripts
fix(hooks): deploy the plugin manifest next to user-scope hook scripts
github-actions commentedon Sep 9, 2026
Triage decision
accept
Proposed labels
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)." }github-actions commentedon Sep 10, 2026
Triage decision
accept
Proposed labels
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
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." }danielmeppiel commentedon Sep 12, 2026
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.
lkshrk commentedon Sep 19, 2026
#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/acceptedwas re-applied on 2026-09-18, but there is noapm-scope:v1record yet.When a maintainer has capacity, please record whether the manifest-only slice (
.claude-plugin/plugin.jsonnext to the deployed hook scripts,prompts/andschemas/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.fix(hooks): deploy the plugin manifest next to user-scope hook scripts
festo commentedon Sep 28, 2026
Another reproduction of this, with a widely used package, wher manifest-only fix would not help.
Setup (APM 0.31.0 8fd10ac, Linux):
targets:
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:
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/):So on both targets, every SessionStart hook injects an error where the skill content should be:
The command exits 0, so no harness shows an error.
danielmeppiel commentedon Sep 28, 2026
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.