From f58f75d3cb18e4571856fb4dac50e3d89fcd3701 Mon Sep 17 00:00:00 2001 From: Oleksii Hryshyn Date: Mon, 7 Sep 2026 16:26:51 +0300 Subject: [PATCH 1/3] fix(utils): convert Windows hook path backslashes to forward slashes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migration 006 rewrote installed Claude/Gemini hook commands to the absolute codemie binary path but preserved Windows backslashes. When Claude Code executes hooks via bash (Git Bash on Windows), every \X sequence in the path is consumed as an escape — the path collapses to gibberish and every hook fails with: /usr/bin/bash: line 1: C:UserspavelAppDataRoamingnpmcodemie: command not found resolveCodemieBinary() now normalizes all Windows paths to forward slashes on every return branch (getCommandPath, argv1, and the node + .js Windows fallback). No-op on POSIX where paths never have backslashes. Refs: EPMCDME-14762 --- src/utils/__tests__/hook-command.test.ts | 28 ++++++++++++++++++++---- src/utils/hook-command.ts | 18 ++++++++++----- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/utils/__tests__/hook-command.test.ts b/src/utils/__tests__/hook-command.test.ts index 2aeae9b7b..92fbe2a0b 100644 --- a/src/utils/__tests__/hook-command.test.ts +++ b/src/utils/__tests__/hook-command.test.ts @@ -51,23 +51,43 @@ describe('hook-command resolver', () => { spy.mockRestore(); }); - it('resolveCodemieBinary: on Windows, a .js argv[1] fallback is prefixed with the node executable', async () => { + it('resolveCodemieBinary: on Windows, a .js argv[1] fallback is prefixed with node and uses forward slashes', async () => { vi.doMock('../processes.js', () => ({ getCommandPath: vi.fn().mockResolvedValue(null) })); const argvSpy = vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', 'C:\\Users\\u\\app\\codemie.js']); const platSpy = vi.spyOn(process, 'platform', 'get').mockReturnValue('win32'); const execSpy = vi.spyOn(process, 'execPath', 'get').mockReturnValue('C:\\Program Files\\nodejs\\node.exe'); const { resolveCodemieBinary, resolveHookCommand } = await import('../hook-command.js'); const bin = await resolveCodemieBinary(); - // A raw .js path is not invocable as a Windows hook command; prefix node. - expect(bin).toBe('"C:\\Program Files\\nodejs\\node.exe" "C:\\Users\\u\\app\\codemie.js"'); + // Backslashes are converted to forward slashes so bash (Git Bash / WSL) can execute the path. + expect(bin).toBe('"C:/Program Files/nodejs/node.exe" "C:/Users/u/app/codemie.js"'); expect(resolveHookCommand('codemie hook', bin)).toBe( - '"C:\\Program Files\\nodejs\\node.exe" "C:\\Users\\u\\app\\codemie.js" hook', + '"C:/Program Files/nodejs/node.exe" "C:/Users/u/app/codemie.js" hook', ); argvSpy.mockRestore(); platSpy.mockRestore(); execSpy.mockRestore(); }); + it('resolveCodemieBinary: on Windows, getCommandPath result with backslashes is converted to forward slashes', async () => { + vi.doMock('../processes.js', () => ({ + getCommandPath: vi.fn().mockResolvedValue('C:\\Users\\u\\AppData\\Local\\CodeMie\\bin\\codemie.cmd'), + })); + const platSpy = vi.spyOn(process, 'platform', 'get').mockReturnValue('win32'); + const { resolveCodemieBinary } = await import('../hook-command.js'); + expect(await resolveCodemieBinary()).toBe('C:/Users/u/AppData/Local/CodeMie/bin/codemie.cmd'); + platSpy.mockRestore(); + }); + + it('resolveCodemieBinary: on Windows, getCommandPath result with spaces and backslashes is quoted with forward slashes', async () => { + vi.doMock('../processes.js', () => ({ + getCommandPath: vi.fn().mockResolvedValue('C:\\Program Files\\CodeMie\\bin\\codemie.cmd'), + })); + const platSpy = vi.spyOn(process, 'platform', 'get').mockReturnValue('win32'); + const { resolveCodemieBinary } = await import('../hook-command.js'); + expect(await resolveCodemieBinary()).toBe('"C:/Program Files/CodeMie/bin/codemie.cmd"'); + platSpy.mockRestore(); + }); + it('resolveCodemieBinary: on non-Windows, a .js argv[1] fallback stays a bare path (shebang-executable)', async () => { vi.doMock('../processes.js', () => ({ getCommandPath: vi.fn().mockResolvedValue(null) })); const argvSpy = vi.spyOn(process, 'argv', 'get').mockReturnValue(['node', '/home/u/app/codemie.js']); diff --git a/src/utils/hook-command.ts b/src/utils/hook-command.ts index 612a45e4a..7a2a18bfa 100644 --- a/src/utils/hook-command.ts +++ b/src/utils/hook-command.ts @@ -16,24 +16,32 @@ function alwaysQuote(p: string): string { return p.startsWith('"') ? p : `"${p}"`; } +// Convert Windows backslashes to forward slashes so the resolved path survives +// bash (Git Bash / WSL) execution without \X sequences being consumed as escapes. +// No-op on paths that already use forward slashes. See EPMCDME-14035. +function toForwardSlash(p: string): string { + return p.replace(/\\/g, '/'); +} + // Prefer the PATH-resolved shim, then the running entry (argv[1]), then bare `codemie`. // Never throws — it runs in launch-critical hook paths, so errors degrade to the next fallback. export async function resolveCodemieBinary(): Promise { try { const resolved = await getCommandPath('codemie'); - if (resolved) return quoteIfNeeded(resolved); + if (resolved) return quoteIfNeeded(toForwardSlash(resolved)); } catch { // fall through } const argv1 = process.argv[1]; if (argv1) { - // A Windows .js argv[1] is not directly invocable as a hook command — cmd.exe - // needs a `node` prefix; both tokens are quoted to survive spaces. + // A Windows .js argv[1] is not directly invocable as a hook command — bash + // needs a `node` prefix; both tokens use forward slashes and are quoted to + // survive spaces in paths like "C:/Program Files/...". if (process.platform === 'win32' && /\.[cm]?js$/i.test(argv1)) { - return `${alwaysQuote(process.execPath)} ${alwaysQuote(argv1)}`; + return `${alwaysQuote(toForwardSlash(process.execPath))} ${alwaysQuote(toForwardSlash(argv1))}`; } - return quoteIfNeeded(argv1); + return quoteIfNeeded(toForwardSlash(argv1)); } return 'codemie'; From 181eb1bc4a2fffce95ff5734d05046e493a03b66 Mon Sep 17 00:00:00 2001 From: Oleksii Hryshyn Date: Mon, 7 Sep 2026 16:52:01 +0300 Subject: [PATCH 2/3] fix(utils): increase hook timeouts to 10 seconds Raise all hook timeouts to 10 s (Claude: seconds unit; Gemini: ms unit) so slow-starting codemie hook processes are not killed prematurely on Windows where process startup is measurably slower. Refs: EPMCDME-14762 Co-Authored-By: Claude Sonnet 4.6 --- src/agents/plugins/claude/plugin/hooks/hooks.json | 12 ++++++------ src/agents/plugins/gemini/extension/hooks/hooks.json | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/agents/plugins/claude/plugin/hooks/hooks.json b/src/agents/plugins/claude/plugin/hooks/hooks.json index bfd3cb7d7..c301bc6c0 100644 --- a/src/agents/plugins/claude/plugin/hooks/hooks.json +++ b/src/agents/plugins/claude/plugin/hooks/hooks.json @@ -7,7 +7,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 5 + "timeout": 10 }, { "type": "command", @@ -33,7 +33,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 2 + "timeout": 10 }, { "type": "command", @@ -48,7 +48,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 3 + "timeout": 10 } ] } @@ -59,7 +59,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 2 + "timeout": 10 }, { "type": "command", @@ -74,7 +74,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 2 + "timeout": 10 }, { "type": "command", @@ -89,7 +89,7 @@ { "type": "command", "command": "codemie hook", - "timeout": 2 + "timeout": 10 } ] } diff --git a/src/agents/plugins/gemini/extension/hooks/hooks.json b/src/agents/plugins/gemini/extension/hooks/hooks.json index af09a85e9..15dba3320 100644 --- a/src/agents/plugins/gemini/extension/hooks/hooks.json +++ b/src/agents/plugins/gemini/extension/hooks/hooks.json @@ -20,7 +20,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie Session End", - "timeout": 30000 + "timeout": 100000 } ] } @@ -32,7 +32,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie Stop Hook", - "timeout": 5000 + "timeout": 10000 } ] } @@ -44,7 +44,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie User Prompt Submit", - "timeout": 2000 + "timeout": 10000 } ] } @@ -56,7 +56,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie PreCompact Hook", - "timeout": 3000 + "timeout": 10000 } ] } @@ -68,7 +68,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie Permission Request", - "timeout": 3000 + "timeout": 10000 } ] } From 3c3ba2cb024f45cc78d645f67d5a350db0ce5e51 Mon Sep 17 00:00:00 2001 From: Oleksii Hryshyn Date: Mon, 7 Sep 2026 17:15:36 +0300 Subject: [PATCH 3/3] fix(utils): restore Gemini SessionEnd timeout to 30 s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A replace-all edit for 3000→10000 ms accidentally matched the substring inside 30000, producing 100000 ms. Correct value is 30000 ms (30 s), which was intentional in the original file. Refs: EPMCDME-14762 Co-Authored-By: Claude Sonnet 4.6 --- src/agents/plugins/gemini/extension/hooks/hooks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agents/plugins/gemini/extension/hooks/hooks.json b/src/agents/plugins/gemini/extension/hooks/hooks.json index 15dba3320..edafa4fcb 100644 --- a/src/agents/plugins/gemini/extension/hooks/hooks.json +++ b/src/agents/plugins/gemini/extension/hooks/hooks.json @@ -20,7 +20,7 @@ "type": "command", "command": "codemie hook", "name": "CodeMie Session End", - "timeout": 100000 + "timeout": 30000 } ] }