Reject Claude manifest hooks refs to the auto-loaded hooks/hooks.json - #113
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a Claude-target “host-contract” validation rule to prevent a real-world plugin load failure: Claude Code auto-loads hooks/hooks.json and errors if the manifest’s hooks field also references that same file. The change updates the architecture spec to document the new validator category and adds regression coverage plus a decision doc describing the underlying design collision.
Changes:
- Add a Claude validator that hard-fails when
.claude-plugin/plugin.jsonhooksreferenceshooks/hooks.json(including normalized variants and array entries). - Add targeted regression tests covering firing/non-firing cases for the duplicate-reference rule.
- Update the architecture spec to define “host-contract checks” and document the new Claude rule; add a draft decision document and a changeset entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/targets/claude/validate.ts | Adds a host-contract validation that rejects manifest refs to Claude’s auto-loaded hooks/hooks.json. |
| packages/core/src/targets/claude/validate.test.ts | Adds regression tests for the new duplicate hooks reference rule. |
| docs/specs/hooks-json-autoload-collision.md | Adds a draft decision document describing the broader hooks artifact collision and resolution options. |
| docs/specs/architecture.md | Updates spec version and documents the new host-contract validator category and rule §10.1.5. |
| .changeset/curly-poems-repeat.md | Publishes the behavior change in @ai-plugin-marketplace/core as a minor release note. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ks/hooks.json
Claude Code auto-loads <pluginDir>/hooks/hooks.json and hard-errors when the
plugin manifest's `hooks` field names that same file ("Duplicate hooks file
detected ... The standard hooks/hooks.json is loaded automatically, so
manifest.hooks should only reference additional hook files").
`aipm validate` now emits a hard `schema-invalid` finding when the Claude
manifest's `hooks` field -- a string, or any string entry when it is an array --
normalizes to the plugin-relative path `hooks/hooks.json`. The check reads the
raw manifest rather than the schema-parsed value, so the array form (legal in
Claude Code, unmodeled by claudePluginManifestSchema) is still caught.
architecture.md gains §10.1 category 5 (host-contract checks) and §10.1.5
documenting the rule, and notes the still-open collision between Claude's
auto-load and the shared Codex/Gemini hooks/hooks.json artifact.
mike-north
force-pushed
the
claude/hooks-json-autoload-guard
branch
from
August 27, 2026 18:14
63ee30d to
3314fdb
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Claude Code auto-loads a plugin's
hooks/hooks.jsonand refuses to load any plugin whose.claude-plugin/plugin.jsonhooksfield also names that file:This shipped as a real end-user failure in a downstream marketplace plugin (mike-north/ofocus#93 fixed the instance), so
aipm validateshould catch the pattern before publish.What changed
packages/core/src/targets/claude/validate.ts): a hardschema-invalidfinding when the manifest'shooksfield — a string, or any string entry when it's an array — normalizes to the plugin-relativehooks/hooks.json. The check reads the raw manifest JSON rather than the schema-parsed value becauseclaudePluginManifestSchemamodelshooksasstring | Record, while Claude acceptsstring | array | object; the array form would otherwise be invisible. The rule targets the reference, not the file — carryinghooks/hooks.jsonas the shared Codex/Gemini artifact with no manifest ref draws no finding.docs/specs/architecture.md, 0.4.2 → 0.4.3): §10.1 gains a fifth validator category, host-contract checks, with §10.1.5 as its first normative rule; §10.2 records host-contract violations as hard.validate.test.ts(firing: plain ref,hooks/./hooks.jsonnormalization, array form; non-firing:claude.jsonref, auto-loaded file with no manifest ref, similarly-named file), with the Claude docs contract and observed error text cited in the file header.docs/specs/hooks-json-autoload-collision.md, Draft): the guard papers over a deeper design conflict — Claude's auto-load vs.hooks/hooks.jsonas the shared Codex/Gemini artifact (adapter-system.md D6). Notably, the in-plugin-dir Geminihooks/hooks.jsonis read by nobody today (the Gemini bundler regenerates it intodist/independently), so a Gemini-format file can be silently auto-loaded as Claude hooks for no benefit. The doc lays out affected target combinations and resolution options with a staged recommendation; nothing in it is implemented here.Changeset included (minor,
@ai-plugin-marketplace/core).Note:
targets/kiro/bundle.test.tsandtargets/gemini/bundle.test.ts(template-repo parity) fail identically onmain; they're unrelated to this change.