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) => {