From 665c51b9036d8266ae250635093688156476252c Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Mon, 17 Aug 2026 06:10:16 -0700 Subject: [PATCH] Fix Windows hook migration dedupe Normalize hook command separators before matching Impeccable-owned entries so updates replace legacy Windows guards instead of duplicating them.\n\nAI assistance: Codex implemented and validated this change under maintainer authorization. --- cli/bin/commands/skills.mjs | 3 ++- tests/skills-cli.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cli/bin/commands/skills.mjs b/cli/bin/commands/skills.mjs index c62a2558b..a36f67900 100644 --- a/cli/bin/commands/skills.mjs +++ b/cli/bin/commands/skills.mjs @@ -1534,7 +1534,8 @@ function hookInstalledForProvider(root, provider) { function valueHasImpeccableHookMarker(value) { if (typeof value === 'string') { - return IMPECCABLE_HOOK_COMMAND_MARKERS.some(marker => value.includes(marker)); + const normalized = value.replace(/\\/g, '/'); + return IMPECCABLE_HOOK_COMMAND_MARKERS.some(marker => normalized.includes(marker)); } if (Array.isArray(value)) return value.some(valueHasImpeccableHookMarker); if (value && typeof value === 'object') { diff --git a/tests/skills-cli.test.js b/tests/skills-cli.test.js index f4e9b799d..e024f0b78 100644 --- a/tests/skills-cli.test.js +++ b/tests/skills-cli.test.js @@ -1649,6 +1649,35 @@ describe('hook manifest merge helpers', () => { 'node .cursor/skills/impeccable/scripts/hook-before-edit.mjs', ]); }); + + test('mergeHookManifests replaces legacy Windows-path Claude hooks (#604)', () => { + const legacyPath = 'C:\\Users\\alice\\.claude\\skills\\impeccable\\scripts\\hook.mjs'; + const legacyCommand = `[ ! -f "${legacyPath}" ] || node "${legacyPath}"`; + const freshCommand = `node -e "guard" "${legacyPath}"`; + const merged = mergeHookManifests( + { + hooks: { + PostToolUse: [{ matcher: 'Edit|Write|MultiEdit', hooks: [ + { type: 'command', command: legacyCommand }, + ] }], + Stop: [{ hooks: [{ type: 'command', command: legacyCommand }] }], + }, + }, + { + hooks: { + PostToolUse: [{ matcher: 'Edit|Write|MultiEdit', hooks: [ + { type: 'command', command: freshCommand }, + ] }], + Stop: [{ hooks: [{ type: 'command', command: freshCommand }] }], + }, + }, + ); + + expect(merged.hooks.PostToolUse).toHaveLength(1); + expect(merged.hooks.Stop).toHaveLength(1); + expect(merged.hooks.PostToolUse[0].hooks[0].command).toBe(freshCommand); + expect(merged.hooks.Stop[0].hooks[0].command).toBe(freshCommand); + }); }); // ─── Hook command path resolution (issue #399, part 1) ───────────────────────