From 2061919b1abd7cd7feb8961039fa56b6621b2924 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 6 Aug 2026 06:14:58 -0400 Subject: [PATCH] fix(#3029): remove redundant hooks declaration from plugin manifest (#3113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- .changeset/sharp-cranes-hum.md | 5 +++++ .claude-plugin/plugin.json | 3 +-- tests/fixtures/plugin-manifest-schema.json | 3 +-- tests/issue-766-plugin-manifest.test.cjs | 9 +++++---- 4 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 .changeset/sharp-cranes-hum.md diff --git a/.changeset/sharp-cranes-hum.md b/.changeset/sharp-cranes-hum.md new file mode 100644 index 000000000..733e53353 --- /dev/null +++ b/.changeset/sharp-cranes-hum.md @@ -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) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index dd0925945..f6ebd3714 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -19,6 +19,5 @@ "gsd" ], "commands": "./commands/gsd/", - "skills": "./skills/", - "hooks": "./hooks/hooks.json" + "skills": "./skills/" } diff --git a/tests/fixtures/plugin-manifest-schema.json b/tests/fixtures/plugin-manifest-schema.json index 886e9497a..e7dddd8d4 100644 --- a/tests/fixtures/plugin-manifest-schema.json +++ b/tests/fixtures/plugin-manifest-schema.json @@ -11,8 +11,7 @@ "repository", "homepage", "license", - "commands", - "hooks" + "commands" ], "additionalProperties": true, "properties": { diff --git a/tests/issue-766-plugin-manifest.test.cjs b/tests/issue-766-plugin-manifest.test.cjs index 80a94124f..329e29a9f 100644 --- a/tests/issue-766-plugin-manifest.test.cjs +++ b/tests/issue-766-plugin-manifest.test.cjs @@ -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) => {