fix(#3029): remove redundant hooks declaration from plugin manifest (#3113)

* fix(#3029): remove redundant hooks declaration from plugin manifest

.claude-plugin/plugin.json declared "hooks": "./hooks/hooks.json"
explicitly. Claude Code already auto-loads hooks/hooks.json by default,
so the explicit declaration caused a duplicate-rejection that silently
disabled every hook the plugin ships — security guards, monitors,
injection scanners. The failure had no visible signal in normal use.

Removed the redundant hooks field. Updated the manifest test to assert
ABSENCE of the field and that the auto-loaded file still exists on disk.

* chore(#3029): backfill changeset PR number 3113

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-06 06:14:58 -04:00
committed by GitHub
parent 60cf18999b
commit 2061919b1a
4 changed files with 12 additions and 8 deletions

View File

@@ -0,0 +1,5 @@
---
type: Fixed
pr: 3113
---
**Claude Code plugin installs no longer silently disable all hooks** — the plugin manifest (`.claude-plugin/plugin.json`) explicitly declared `hooks/hooks.json`, which Claude Code also auto-loads by default, causing a duplicate-declaration rejection that silently disabled every hook (security guards, monitors, injection scanners). The redundant declaration is removed; Claude Code's auto-load path handles it. (#3029)

View File

@@ -19,6 +19,5 @@
"gsd"
],
"commands": "./commands/gsd/",
"skills": "./skills/",
"hooks": "./hooks/hooks.json"
"skills": "./skills/"
}

View File

@@ -11,8 +11,7 @@
"repository",
"homepage",
"license",
"commands",
"hooks"
"commands"
],
"additionalProperties": true,
"properties": {

View File

@@ -92,11 +92,12 @@ describe('A: .claude-plugin/plugin.json', () => {
assert.ok(mdFiles.length > 0, `commands dir must contain at least one .md file`);
});
test('hooks field is "./hooks/hooks.json" and that file exists', (t) => {
test('#3029: hooks field is ABSENT — Claude Code auto-loads hooks/hooks.json (explicit declaration caused duplicate-rejection)', (t) => {
if (!manifest) { t.skip('manifest could not be parsed'); return; }
assert.equal(manifest.hooks, './hooks/hooks.json', 'hooks must be "./hooks/hooks.json"');
const resolvedHooks = path.resolve(path.dirname(PLUGIN_JSON_PATH), '..', manifest.hooks);
assert.ok(fs.existsSync(resolvedHooks), `resolved hooks file must exist: ${resolvedHooks}`);
assert.ok(!manifest.hooks, 'plugin.json must NOT declare hooks — Claude Code auto-loads hooks/hooks.json; an explicit declaration causes a duplicate-rejection that silently disables all hooks (#3029)');
// The auto-loaded hooks file must still exist on disk.
const resolvedHooks = path.resolve(path.dirname(PLUGIN_JSON_PATH), '..', 'hooks', 'hooks.json');
assert.ok(fs.existsSync(resolvedHooks), `hooks/hooks.json must exist for auto-loading: ${resolvedHooks}`);
});
test('no "$schema" key (intentionally omitted)', (t) => {