Skip to content
Closed
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 .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Bolt's Journal - Critical Performance Learnings

## 2026-08-09 - [State Pollution in Concurrent Tests under Bun]
**Learning:** Mutating global properties like `process.platform` in concurrent asynchronous test suites causes state pollution and race conditions across tests under Bun.
**Action:** Avoid mutating `process.platform` globally. Instead, pass `platform` or mock dependencies explicitly, or use helper functions that isolate the platform environment.
29 changes: 15 additions & 14 deletions src/commands/init/agent-definitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ describe("detectAgents", () => {
});

describe("scanAgents", () => {
function lookupCommandFor(platform: string = process.platform): string {
function lookupCommandFor(platform: string = "linux"): string {
return platform === "win32" ? "where" : "which";
}

Expand All @@ -1352,10 +1352,16 @@ describe("scanAgents", () => {
configFiles?: Record<string, string>;
existingFiles?: string[];
execResults?: Record<string, ExecResult | Error>;
pathPlatform?: "posix" | "win32";
pathPlatform?: "posix" | "win32" | "darwin" | "linux";
}) {
const isWin32 = opts.pathPlatform === "win32";
const platform =
opts.pathPlatform === "posix"
? "linux"
: (opts.pathPlatform ??
(process.platform === "win32" ? "linux" : process.platform));
const isWin32 = platform === "win32";
const fs = createMockFileSystemService({
platform,
getHomeDir: mock(() => (isWin32 ? "C:\\Users\\test" : "/home/test")),
joinPath: mock((...segments: string[]) =>
isWin32 ? win32.join(...segments) : segments.join("/"),
Expand All @@ -1374,6 +1380,7 @@ describe("scanAgents", () => {
),
});
const execService = createMockExecService({
platform,
exec: mock(async (cmd: string, args: string[], _options?: unknown) => {
const key = `${cmd} ${args.join(" ")}`;
if (opts.execResults && key in opts.execResults) {
Expand Down Expand Up @@ -2620,23 +2627,17 @@ describe("scanAgents", () => {
};

describe(`comprehensive all-agents scenarios (${platform})`, () => {
const pathPlatform = platform === "win32" ? "win32" : "posix";
const createScenarioScanMocks = (
scenarioOpts: Parameters<typeof createScanMocks>[0],
) => createScanMocks({ ...scenarioOpts, pathPlatform });
const originalPlatform = process.platform;
beforeAll(() => {
Object.defineProperty(process, "platform", {
value: platform,
configurable: true,
) =>
createScanMocks({
...scenarioOpts,
pathPlatform: platform as "posix" | "win32" | "darwin" | "linux",
});
beforeAll(() => {
opts.envSetup?.();
});
afterAll(() => {
Object.defineProperty(process, "platform", {
value: originalPlatform,
configurable: true,
});
opts.envTeardown?.();
});

Expand Down
27 changes: 18 additions & 9 deletions src/commands/init/agent-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,9 @@ export interface AgentDefinition {
* Linux: ~/.config/<app>
*/
function getAppDataPath(fs: FileSystemService, appName: string): string {
const platform = fs.platform ?? process.platform;
const home = fs.getHomeDir();
switch (process.platform) {
switch (platform) {
case "win32":
return fs.joinPath(
process.env.APPDATA ?? fs.joinPath(home, "AppData", "Roaming"),
Expand All @@ -232,8 +233,9 @@ function getAppDataPath(fs: FileSystemService, appName: string): string {
* Linux: $XDG_DATA_HOME (or ~/.local/share fallback)
*/
function getUserDataRoot(fs: FileSystemService): string {
const platform = fs.platform ?? process.platform;
const home = fs.getHomeDir();
switch (process.platform) {
switch (platform) {
case "win32":
return process.env.APPDATA ?? fs.joinPath(home, "AppData", "Roaming");
case "darwin":
Expand All @@ -244,7 +246,8 @@ function getUserDataRoot(fs: FileSystemService): string {
}

function getOpenCodeConfigDir(fs: FileSystemService): string {
if (process.platform === "win32") {
const platform = fs.platform ?? process.platform;
if (platform === "win32") {
return fs.joinPath(getUserDataRoot(fs), "opencode");
}
return fs.joinPath(fs.getHomeDir(), ".config", "opencode");
Expand Down Expand Up @@ -417,7 +420,8 @@ async function isExecutableAvailable(
executable: string,
): Promise<boolean> {
try {
const lookupCommand = process.platform === "win32" ? "where" : "which";
const platform = exec.platform ?? process.platform;
const lookupCommand = platform === "win32" ? "where" : "which";
const result = await exec.exec(lookupCommand, [executable], {
timeoutMs: BINARY_LOOKUP_TIMEOUT_MS,
});
Expand Down Expand Up @@ -445,14 +449,17 @@ type PiGlobalBinProbe = (typeof PI_GLOBAL_BIN_PROBES)[number];
const PI_ADAPTER_CONFIGURED_PATTERN =
/(?:^|\s|:)(?:npm:)?pi-mcp-adapter(?:[\s@:]|$)/i;

function getPiExecutableNames(): string[] {
return process.platform === "win32" ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
function getPiExecutableNames(
platform: NodeJS.Platform = process.platform,
): string[] {
return platform === "win32" ? ["pi.cmd", "pi.exe", "pi"] : ["pi"];
}

async function runGlobalBinProbe(
exec: ExecService,
probe: PiGlobalBinProbe,
): Promise<string | null> {
const platform = exec.platform ?? process.platform;
try {
const result = await exec.exec(probe.command, [...probe.args], {
timeoutMs: GLOBAL_BIN_PROBE_TIMEOUT_MS,
Expand All @@ -467,7 +474,7 @@ async function runGlobalBinProbe(
if (!probePath) {
return null;
}
if (probe.output === "prefix" && process.platform !== "win32") {
if (probe.output === "prefix" && platform !== "win32") {
return fsJoinPathLike(probePath, "bin");
}
return probePath;
Expand All @@ -484,6 +491,7 @@ async function detectPiExecutable(
exec: ExecService,
fs: FileSystemService,
): Promise<ResolvedAgentCommand | null> {
const platform = exec.platform ?? fs.platform ?? process.platform;
if (await resolveExecutableFromPath(exec, "pi")) {
return { command: "pi" };
}
Expand All @@ -493,7 +501,7 @@ async function detectPiExecutable(
if (!binDir) {
continue;
}
for (const executableName of getPiExecutableNames()) {
for (const executableName of getPiExecutableNames(platform)) {
const candidate = fs.joinPath(binDir, executableName);
if (await fs.exists(candidate)) {
return { command: candidate };
Expand Down Expand Up @@ -649,8 +657,9 @@ const claudeDesktop: AgentDefinition = {
detectionMethod: "path",
setupMethod: "config-file",
detectPaths: (fs) => {
const platform = fs.platform ?? process.platform;
const appData = getAppDataPath(fs, "Claude");
if (process.platform === "win32") {
if (platform === "win32") {
const home = fs.getHomeDir();
const localAppData =
process.env.LOCALAPPDATA ?? fs.joinPath(home, "AppData", "Local");
Expand Down
5 changes: 5 additions & 0 deletions src/services/exec-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ export class ExecTimeoutError extends Error {
* Abstraction allows for easy testing with mock implementations.
*/
export interface ExecService {
/** Optional platform override, mainly for testing. */
platform?: NodeJS.Platform;

/** Execute a command with arguments and return the result */
exec(
command: string,
Expand All @@ -117,6 +120,8 @@ export interface ExecService {
* Callers must not pass untrusted input as command or args.
*/
export class ExecServiceImpl implements ExecService {
platform: NodeJS.Platform = process.platform;

async exec(
command: string,
args: string[],
Expand Down
5 changes: 5 additions & 0 deletions src/services/filesystem-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import { dirname, join } from "node:path";
* Abstraction allows for easy testing with mock implementations.
*/
export interface FileSystemService {
/** Optional platform override, mainly for testing. */
platform?: NodeJS.Platform;

/** Read file contents as string */
readFile(path: string): Promise<string>;

Expand Down Expand Up @@ -71,6 +74,8 @@ export interface FileSystemService {
* Production implementation using node:fs/promises.
*/
export class FileSystemServiceImpl implements FileSystemService {
platform: NodeJS.Platform = process.platform;

async readFile(path: string): Promise<string> {
return readFile(path, "utf-8");
}
Expand Down