Skip to content

fix(plugin): name only claude-code.json in the Claude Code manifest hooks field - #14

Merged
LittleCoinCoin merged 4 commits into
mainfrom
claude/nostalgic-feynman-dfde7a
Sep 14, 2026
Merged

LittleCoinCoin merged 4 commits into
mainfrom
claude/nostalgic-feynman-dfde7a

Conversation

@LittleCoinCoin

@LittleCoinCoin LittleCoinCoin commented Sep 14, 2026

Copy link
Copy Markdown
Member

Why

claude plugin install colgrep-mcp@cracking-shells (0.5.0, Claude Code 2.1.270) fails to load:

Hook load failed: Duplicate hooks file detected: ./hooks/hooks.json resolves to already-loaded file
~/.claude/plugins/cache/cracking-shells/colgrep-mcp/0.5.0/hooks/hooks.json. The standard hooks/hooks.json
is loaded automatically, so manifest.hooks should only reference additional hook files.

Claude Code auto-loads hooks/hooks.json and reads manifest.hooks as additional files only (the plugins reference documents the field with the example ./my-extra-hooks.json). Codex reads the same field the other way round: it discovers hooks/hooks.json only when the manifest defines no hooks, and an explicit value replaces that discovery. The manifest listed both files since 0.4.0, so every marketplace install of 0.4.0 and 0.5.0 loses the hooks, the MCP server and the skill at once. claude --plugin-dir ., plugin details and plugin validate do not run the duplicate check, which is why the tree-level gates stayed green.

What

  • .claude-plugin/plugin.json: hooks becomes the single string ./hooks/worktree-remove.json. The Codex manifest keeps ./hooks/hooks.json.
  • hooks/claude-code.json is renamed hooks/worktree-remove.json: hooks.json is the name both loaders claim by default, and a second generic name beside it is the kind a loader could claim next. Rule: every file other than hooks.json holds exactly one event not every harness knows and is named after it.
  • server/tests/test_hooks.py: enforces the per-event file stem, that the Claude Code manifest names exactly the extra files and never hooks.json, and that the Codex manifest names hooks.json.
  • README.md, AGENTS.md, dev/skills/stack-traps (new #hooks-manifest-duplicate entry with the two loaders' semantics and the scratch-marketplace check) and the drift-tests table follow.

Verified by installing the tree through a scratch directory marketplace: claude plugin list shows ✔ enabled and plugin details lists all four hooks (SessionStart, SubagentStart, PreToolUse, WorktreeRemove).

A patch release (0.5.1) is what puts a loadable manifest in front of marketplace users.

Gates

pytest, ruff check, ruff format --check, cz check --rev-range main..HEAD, claude plugin validate . and ./dev: green locally; CI on the PR.

🤖 Generated with Claude Code

LittleCoinCoin and others added 4 commits September 15, 2026 00:45
…ooks field

Claude Code loads hooks/hooks.json automatically and reads manifest.hooks as
additional files only. Listing the default file too makes every marketplace
install fail to load ("Duplicate hooks file detected", Claude Code 2.1.270,
reproduced on the 0.4.0 and 0.5.0 installs), taking the hooks, the MCP server
and the skill down with it, while `--plugin-dir`, `plugin details` and
`plugin validate` accept the same tree silently. The test now pins the single
string and the reason; the Codex manifest keeps naming hooks/hooks.json, as
Codex documents no auto-load.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…stack-traps

The README hooks section and the stack-traps reference said the Claude Code
manifest named both hook files; it now names one, and the new
`#hooks-manifest-duplicate` entry gives the symptom, the loader rule, the
observed Claude Code version and the scratch-marketplace check that reproduces
an install where `--plugin-dir` cannot.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
hooks/claude-code.json becomes hooks/worktree-remove.json. hooks.json is the
name both loaders claim by default, and the trap fixed in the previous commit
came from naming that default in the manifest; a second generic name beside
it is the kind a loader could claim next. Every file other than hooks.json now
holds exactly one event not every harness knows and is named after it, and
the test enforces the stem. The manifest test also records why the two
manifests differ: Claude Code adds the field's files to the auto-loaded
hooks.json, Codex replaces default discovery with the field.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…' field semantics

README, AGENTS.md, the drift-tests table and the stack-traps entry now name
hooks/worktree-remove.json, state the one-file-per-non-portable-event rule
with its reason, and record that Codex's `hooks` field replaces default
discovery where Claude Code's adds to it, so no single manifest value serves
both.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@LittleCoinCoin
LittleCoinCoin merged commit bb414cc into main Sep 14, 2026
5 checks passed
@LittleCoinCoin
LittleCoinCoin deleted the claude/nostalgic-feynman-dfde7a branch September 14, 2026 23:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant