Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/fix-windows-git-bash-path-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@moonshot-ai/kimi-code": patch
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Add the SDK to the release changeset

For Windows SDK users who create sessions, this fix is part of the published SDK artifact too: packages/node-sdk/tsdown.config.ts aliases @moonshot-ai/agent-core to source and alwaysBundles @moonshot-ai/*, while packages/node-sdk/src/rpc.ts constructs KimiCore from that bundled code. With only @moonshot-ai/kimi-code listed here, the release PR will not version/publish @moonshot-ai/kimi-code-sdk, so npm SDK users will not receive the Git Bash detection fix.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 45786e3 — added @moonshot-ai/kimi-code-sdk: patch to the changeset.

"@moonshot-ai/kimi-code-sdk": patch
---

Fix Git Bash path detection on Windows by also searching `usr\bin\bash.exe` locations, which is where bash lives in many Git for Windows installations where `bin\bash.exe` does not exist.
22 changes: 14 additions & 8 deletions packages/kaos/src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,25 @@ async function locateWindowsGitBash(deps: EnvironmentDeps): Promise<string> {
if (gitExe !== undefined) {
const inferred = inferGitBashFromGitExe(gitExe);
if (inferred !== undefined) {
checked.push(inferred);
if (await deps.isFile(inferred)) {
return inferred;
for (const path of inferred) {
checked.push(path);
if (await deps.isFile(path)) {
return path;
}
}
}
}

const candidates: string[] = [
'C:\\Program Files\\Git\\bin\\bash.exe',
'C:\\Program Files\\Git\\usr\\bin\\bash.exe',
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
'C:\\Program Files (x86)\\Git\\usr\\bin\\bash.exe',
];
const localAppData = deps.env['LOCALAPPDATA']?.trim();
if (localAppData !== undefined && localAppData.length > 0) {
candidates.push(`${localAppData}\\Programs\\Git\\bin\\bash.exe`);
candidates.push(`${localAppData}\\Programs\\Git\\usr\\bin\\bash.exe`);
}
for (const candidate of candidates) {
checked.push(candidate);
Expand All @@ -123,17 +128,18 @@ async function locateWindowsGitBash(deps: EnvironmentDeps): Promise<string> {
}

// Most Git for Windows installs put `git.exe` in `<root>\cmd\git.exe`,
// with bash at `<root>\bin\bash.exe`. Portable installs sometimes put
// both in `<root>\bin\`. Walk back to the parent of `cmd` / `bin` and
// re-anchor under `bin\bash.exe`.
function inferGitBashFromGitExe(gitExe: string): string | undefined {
// with bash at `<root>\bin\bash.exe` (a wrapper) or `<root>\usr\bin\bash.exe`
// (the real MSYS2 shell). Walk back to the parent of `cmd` / `bin` and
// return both candidates so the caller can try them in preference order.
function inferGitBashFromGitExe(gitExe: string): string[] | undefined {
const sep = gitExe.includes('\\') ? '\\' : '/';
const parts = gitExe.split(sep);
for (let i = parts.length - 2; i >= 0; i -= 1) {
const segment = parts[i];
if (segment === 'cmd' || segment === 'bin') {
const root = parts.slice(0, i).join(sep);
return root.length === 0 ? `bin${sep}bash.exe` : `${root}${sep}bin${sep}bash.exe`;
const prefix = root.length === 0 ? '' : `${root}${sep}`;
return [`${prefix}bin${sep}bash.exe`, `${prefix}usr${sep}bin${sep}bash.exe`];
}
}
return undefined;
Expand Down
34 changes: 34 additions & 0 deletions packages/kaos/test/environment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,18 @@ describe('detectEnvironment', () => {
expect(env.shellPath).toBe('C:\\Program Files\\Git\\bin\\bash.exe');
});

it('infers Git Bash from usr/bin when bin/bash.exe is missing', async () => {
const env = await detectEnvironment(
stubDeps({
platform: 'win32',
executables: { 'git.exe': 'D:\\Program Files\\Git\\cmd\\git.exe' },
existingPaths: ['D:\\Program Files\\Git\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellName).toBe('bash');
expect(env.shellPath).toBe('D:\\Program Files\\Git\\usr\\bin\\bash.exe');
});

it('falls back to the well-known Program Files install location', async () => {
const env = await detectEnvironment(
stubDeps({
Expand All @@ -167,6 +179,16 @@ describe('detectEnvironment', () => {
expect(env.shellPath).toBe('C:\\Program Files\\Git\\bin\\bash.exe');
});

it('falls back to usr/bin under Program Files when bin/bash.exe is missing', async () => {
const env = await detectEnvironment(
stubDeps({
platform: 'win32',
existingPaths: ['C:\\Program Files\\Git\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellPath).toBe('C:\\Program Files\\Git\\usr\\bin\\bash.exe');
});

it('falls back to LOCALAPPDATA install when present', async () => {
const env = await detectEnvironment(
stubDeps({
Expand All @@ -178,6 +200,17 @@ describe('detectEnvironment', () => {
expect(env.shellPath).toBe('C:\\Users\\me\\AppData\\Local\\Programs\\Git\\bin\\bash.exe');
});

it('falls back to usr/bin under LOCALAPPDATA when bin/bash.exe is missing', async () => {
const env = await detectEnvironment(
stubDeps({
platform: 'win32',
env: { LOCALAPPDATA: 'C:\\Users\\me\\AppData\\Local' },
existingPaths: ['C:\\Users\\me\\AppData\\Local\\Programs\\Git\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellPath).toBe('C:\\Users\\me\\AppData\\Local\\Programs\\Git\\usr\\bin\\bash.exe');
});

it('throws KaosShellNotFoundError when no Git Bash candidate is found', async () => {
const error = await detectEnvironment(
stubDeps({
Expand Down Expand Up @@ -209,6 +242,7 @@ describe('detectEnvironment', () => {
);
expect(error.message).toContain('D:\\custom\\bash.exe');
expect(error.message).toContain('C:\\Program Files\\Git\\bin\\bash.exe');
expect(error.message).toContain('C:\\Program Files\\Git\\usr\\bin\\bash.exe');
});

// ── arch / version passthrough ─────────────────────────────────────
Expand Down