Skip to content

Claude adapter: plugin.json "hooks" re-registers the auto-loaded hooks/hooks.json, so Claude Code ≥2.1.22 refuses the whole plugin (no MCP tools, no hooks) #463

Description

@ScriptedAlchemy

Symptom in cargo-hauler

ScriptedAlchemy/cargo-hauler#48 — Claude Code 2.1.255 refuses the whole plugin (Status: ✘ failed to load; no mcp__cargo-hauler__* tools, no hooks in the session). claude plugin list --json carries:

Hook load failed: Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file
<plugin>/hooks/hooks.json. The standard hooks/hooks.json is loaded automatically, so manifest.hooks
should only reference additional hook files.

The shipped artifact/claude/.claude-plugin/plugin.json (built with the pkg.pr.new preview of main 886b192) is:

{"author":{"name":"cargo-hauler"},"description":"","hooks":"./hooks/hooks.json","name":"cargo-hauler","version":"0.4.1"}

Claude Code has auto-loaded hooks/hooks.json since v2.1.22 and rejects a manifest that names the same file again (same failure reported against other plugins: affaan-m/ECC#103, ayghri/i-have-adhd#61). The current plugins reference lists hooks/hooks.json as the default location and shows the manifest hooks field only for a custom path ("hooks": "./config/hooks.json"). The hook set is dropped, not deduplicated, and the plugin is marked failed, so MCP servers and skills go with it.

Root cause in agent-bundle (main 10a98a0fb)

  • packages/agent-bundle/src/adapters/claude.ts:3164 — the plugin manifest always gets hooks: \./${hookContract.manifestPath}`` whenever a hook document exists.
  • packages/agent-bundle/src/adapters/claude.ts:423manifestPath: 'hooks/hooks.json', i.e. exactly the auto-loaded default.
  • packages/agent-bundle/src/adapters/schemas/claude/plugin.schema.json:150 — the vendored manifest schema pins "hooks": { "const": "./hooks/hooks.json" }, so the adapter cannot emit anything else and agent-bundle build validates the broken shape as correct.
  • packages/agent-bundle/src/adapters/codex.ts:1192 emits the same key for Codex. Codex 0.147.0 documents hooks/hooks.json as the default or a manifest path (capabilities/codex-0.147.0.json:798); it is not known to reject the duplicate, but the same fix (omit the key when the document is at the default path) is worth applying or explicitly proving harmless there.

The install proof in cargo-hauler PR 34 reported doctor --host claude → installed copy: current for this artifact because doctor never reads the plugin's load status (filed separately as #464).

Workaround currently in cargo-hauler

None — the manifest is generated. Users can only hand-edit ~/.claude/plugins/cache/cargo-hauler-marketplace/cargo-hauler/<version>/.claude-plugin/plugin.json after install.

Proposed fix

  • In claude.ts, omit the hooks manifest key when the emitted hook document lives at hooks/hooks.json (always, today). Keep the key only if a future adapter emits hooks at a non-default path.
  • Relax schemas/claude/plugin.schema.json accordingly (drop the const, or drop the property) and add a PROVENANCE note citing the Claude Code duplicate-hooks rule.
  • Same treatment for codex.ts:1192 once verified against a live codex plugin list --json, or a PROVENANCE row proving Codex accepts the duplicate.
  • Regression: the Claude fixture/contract-matrix test asserts plugin.json has no hooks key when hooks are emitted at the default path.

Acceptance

  • A fresh agent-bundle build for a project with event routes emits artifact/claude/.claude-plugin/plugin.json without hooks; hooks/hooks.json is still present and passes claude plugin validate.
  • Installing that pack via marketplace on Claude Code ≥ 2.1.255 shows claude plugin list --json with errors: [] and the plugin enabled; the MCP tools appear in the session tool list and the SessionStart/PreToolUse/Stop hooks fire (note [BUG] Misleading "duplicate hooks" warning for marketplace-installed plugins anthropics/claude-code#18265 claims hooks may not auto-load for marketplace installs — the acceptance run must prove hooks fire, not only that the load error is gone).
  • docs/diagnostics.md / hosts reference: no change unless a diagnostic is added.

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

    bugSomething isn't workinghost-integrationAgent host capabilities, projection, discovery, and lifecycle

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions