fix(plugins): stop stripLegacyHooks from deleting the plugin's own hooks
claude plugin install materializes the plugin's inline hooks into
~/.claude/settings.json itself, with \${CLAUDE_PLUGIN_ROOT} resolved to the
actual cache path — confirmed by installing the plugin for real and
inspecting the file. Those entries also contain "hook-handler.js", so
isOurEntry()'s plain substring match could not tell a legitimate
plugin-installed hook from a leftover npm run install-hooks entry: every
SessionStart would have stripped the plugin's own working hooks right back
out. isCheckoutHookEntry() only removes entries whose command does NOT
resolve under ~/.claude/plugins/cache/. plugin-doctor.js's duplicate-hook
count uses the same predicate.
This commit is contained in:
@@ -192,6 +192,49 @@ describe("legacy hook cleanup", () => {
|
||||
assert.ok(fs.existsSync(`${settings}.ccam-bak`), "must back up before writing");
|
||||
});
|
||||
|
||||
it("never removes the plugin's own cache-resolved hook entries", () => {
|
||||
// Regression: `claude plugin install` materializes the plugin's inline
|
||||
// hooks into settings.json itself, with ${CLAUDE_PLUGIN_ROOT} resolved to
|
||||
// the cache path — confirmed against a real install. Those entries also
|
||||
// contain "hook-handler.js", so a naive substring check would strip them
|
||||
// right along with genuine checkout leftovers, killing the plugin's own
|
||||
// hooks on every session start.
|
||||
const cachePath = path.join(
|
||||
TMP_HOME,
|
||||
"plugins",
|
||||
"cache",
|
||||
"claude-code-agent-monitor-plugins",
|
||||
"ccam",
|
||||
"abc123",
|
||||
"scripts",
|
||||
"hook-handler.js"
|
||||
);
|
||||
fs.writeFileSync(
|
||||
settings,
|
||||
JSON.stringify({
|
||||
hooks: {
|
||||
PreToolUse: [
|
||||
{
|
||||
matcher: "*",
|
||||
hooks: [{ type: "command", command: `node "${cachePath}" PreToolUse` }],
|
||||
},
|
||||
{
|
||||
matcher: "*",
|
||||
hooks: [
|
||||
{ type: "command", command: "node /repo/scripts/hook-handler.js PreToolUse" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
);
|
||||
const removed = boot.stripLegacyHooks(settings);
|
||||
assert.equal(removed, 1);
|
||||
const after = JSON.parse(fs.readFileSync(settings, "utf8"));
|
||||
assert.equal(after.hooks.PreToolUse.length, 1);
|
||||
assert.match(JSON.stringify(after.hooks.PreToolUse), /plugins.*cache/);
|
||||
});
|
||||
|
||||
it("leaves a file with no CCAM hooks untouched and writes no backup", () => {
|
||||
fs.writeFileSync(settings, JSON.stringify({ hooks: { Stop: [{ hooks: [] }] } }));
|
||||
assert.equal(boot.stripLegacyHooks(settings), 0);
|
||||
|
||||
Reference in New Issue
Block a user