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..edafa4fcb 100644 --- a/src/agents/plugins/gemini/extension/hooks/hooks.json +++ b/src/agents/plugins/gemini/extension/hooks/hooks.json @@ -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 } ] } 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';