Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/agents/plugins/claude/plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 5
"timeout": 10
},
{
"type": "command",
Expand All @@ -33,7 +33,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 2
"timeout": 10
},
{
"type": "command",
Expand All @@ -48,7 +48,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 3
"timeout": 10
}
]
}
Expand All @@ -59,7 +59,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 2
"timeout": 10
},
{
"type": "command",
Expand All @@ -74,7 +74,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 2
"timeout": 10
},
{
"type": "command",
Expand All @@ -89,7 +89,7 @@
{
"type": "command",
"command": "codemie hook",
"timeout": 2
"timeout": 10
}
]
}
Expand Down
8 changes: 4 additions & 4 deletions src/agents/plugins/gemini/extension/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"type": "command",
"command": "codemie hook",
"name": "CodeMie Stop Hook",
"timeout": 5000
"timeout": 10000
}
]
}
Expand All @@ -44,7 +44,7 @@
"type": "command",
"command": "codemie hook",
"name": "CodeMie User Prompt Submit",
"timeout": 2000
"timeout": 10000
}
]
}
Expand All @@ -56,7 +56,7 @@
"type": "command",
"command": "codemie hook",
"name": "CodeMie PreCompact Hook",
"timeout": 3000
"timeout": 10000
}
]
}
Expand All @@ -68,7 +68,7 @@
"type": "command",
"command": "codemie hook",
"name": "CodeMie Permission Request",
"timeout": 3000
"timeout": 10000
}
]
}
Expand Down
28 changes: 24 additions & 4 deletions src/utils/__tests__/hook-command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
18 changes: 13 additions & 5 deletions src/utils/hook-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
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';
Expand Down
Loading