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
5 changes: 5 additions & 0 deletions .changeset/v2-msys2-bash-detection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Fix bash auto-detection on Windows in the experimental v2 engine when git comes from a native MSYS2 toolchain (ucrt64/clang64/clangarm64).
18 changes: 13 additions & 5 deletions packages/agent-core-v2/src/_base/execEnv/environmentProbe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
* same suite runs identically on any host OS. `probeHostEnvironmentFromNode()`
* bundles the Node defaults for production callers and memoises the promise.
*
* On Windows the probe expects Git Bash (the canonical POSIX shell that ships
* with Git for Windows). If it cannot be located the function throws a plain
* `Error` with the checked paths in the message; the App-scope host-environment
* service catches that at first resolution. Set `KIMI_SHELL_PATH` to override.
* On Windows the probe expects bash from Git for Windows or MSYS2. If it
* cannot be located the function throws a plain `Error` with the checked paths
* in the message; the App-scope host-environment service catches that at first
* resolution. Set `KIMI_SHELL_PATH` to override.
*
* Vendored from `@moonshot-ai/kaos` `environment.ts` — kept as a pure helper
* with no DI dependencies.
Expand Down Expand Up @@ -57,6 +57,14 @@ export interface HostEnvironmentProbeDeps {

const GIT_EXEC_PATH_TIMEOUT_MS = 5_000;

const MINGW_PREFIX_SET: ReadonlySet<string> = new Set([
'mingw32',
'mingw64',
'ucrt64',
'clang64',
'clangarm64',
]);

function resolveOsKind(platform: string): OsKind {
switch (platform) {
case 'darwin':
Expand Down Expand Up @@ -222,7 +230,7 @@ function gitBashCandidatesFromGitExecPath(execPath: string): readonly string[] {
const parts = normalized.split('\\');
for (let i = parts.length - 1; i >= 0; i -= 1) {
const segment = parts[i]?.toLowerCase();
if (segment === 'mingw32' || segment === 'mingw64') {
if (segment !== undefined && MINGW_PREFIX_SET.has(segment)) {
const root = parts.slice(0, i).join('\\');
if (root.length > 0) {
return gitBashCandidatesFromGitRoot(root);
Expand Down
100 changes: 100 additions & 0 deletions packages/agent-core-v2/test/_base/execEnv/environmentProbe.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/**
* Host environment probe — MSYS2 bash detection.
*
* Pins the Windows shell probe against native MSYS2 toolchains: a git whose
* `git --exec-path` reports an `ucrt64` / `clang64` / `clangarm64` prefix
* (e.g. `C:/msys64/ucrt64/libexec/git-core`) must walk back to the MSYS2 root
* and resolve the shared bash at `usr\bin\bash.exe`, instead of failing to
* detect any shell.
*
* All tests expect `probeHostEnvironment()` to be a pure function of injected
* platform probes (no ambient state) so the same suite runs identically on
* macOS/Linux/Windows CI runners.
*
* Ported from `packages/kaos/test/environment.test.ts` (the MSYS2 cases added
* by the bash-detection fix); the v1 file carries the full POSIX / Git for
* Windows / Scoop shim matrix, which the vendored probe shares verbatim.
Comment on lines +14 to +16

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 Remove porting notes from the test header

packages/agent-core-v2/AGENTS.md says comments in this package should not "note porting / skeleton status"; this new header adds provenance about being ported from the kaos suite instead of describing only the test scenario/responsibility. Please drop the porting note or move any necessary rationale into the commit/PR text rather than the source header.

Useful? React with 👍 / 👎.

*/

import { describe, expect, it } from 'vitest';

import {
probeHostEnvironment,
type HostEnvironmentProbeDeps,
} from '#/_base/execEnv/environmentProbe';

interface StubOpts {
readonly platform: string;
readonly env?: Record<string, string | undefined>;
readonly existingPaths?: readonly string[];
readonly execFileResults?: Readonly<Record<string, string>>;
}

/** Build a stub deps bag mimicking Node's `os` + `process` surface. */
function stubDeps(opts: StubOpts): HostEnvironmentProbeDeps {
const existing = new Set(opts.existingPaths ?? []);
return {
platform: opts.platform,
arch: 'x86_64',
release: '1.2.3',
homeDir: 'C:\\Users\\me',
env: opts.env ?? {},
isFile: async (path: string) => existing.has(path),
execFileText: async (file: string, args: readonly string[]) =>
opts.execFileResults?.[execFileKey(file, args)],
};
}

function execFileKey(file: string, args: readonly string[]): string {
return [file, ...args].join('\0');
}

describe('probeHostEnvironment', () => {
it('resolves MSYS2 ucrt64 native git through git --exec-path', async () => {
const gitExe = 'C:\\msys64\\ucrt64\\bin\\git.exe';
const env = await probeHostEnvironment(
stubDeps({
platform: 'win32',
env: { PATH: 'C:\\msys64\\ucrt64\\bin' },
execFileResults: {
[execFileKey(gitExe, ['--exec-path'])]: 'C:/msys64/ucrt64/libexec/git-core\n',
},
existingPaths: [gitExe, 'C:\\msys64\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellName).toBe('bash');
expect(env.shellPath).toBe('C:\\msys64\\usr\\bin\\bash.exe');
});

it('resolves MSYS2 clang64 native git through git --exec-path', async () => {
const gitExe = 'C:\\msys64\\clang64\\bin\\git.exe';
const env = await probeHostEnvironment(
stubDeps({
platform: 'win32',
env: { PATH: 'C:\\msys64\\clang64\\bin' },
execFileResults: {
[execFileKey(gitExe, ['--exec-path'])]: 'C:/msys64/clang64/libexec/git-core\n',
},
existingPaths: [gitExe, 'C:\\msys64\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellName).toBe('bash');
expect(env.shellPath).toBe('C:\\msys64\\usr\\bin\\bash.exe');
});

it('resolves MSYS2 clangarm64 native git through git --exec-path', async () => {
const gitExe = 'C:\\msys64\\clangarm64\\bin\\git.exe';
const env = await probeHostEnvironment(
stubDeps({
platform: 'win32',
env: { PATH: 'C:\\msys64\\clangarm64\\bin' },
execFileResults: {
[execFileKey(gitExe, ['--exec-path'])]: 'C:/msys64/clangarm64/libexec/git-core\n',
},
existingPaths: [gitExe, 'C:\\msys64\\usr\\bin\\bash.exe'],
}),
);
expect(env.shellName).toBe('bash');
expect(env.shellPath).toBe('C:\\msys64\\usr\\bin\\bash.exe');
});
});
Loading