# Files changed · PR \#9567 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/9567/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[test\(server\): run fake provider CLIs through a Node stub on every host](https://git.cafe/pingdotgg/t3code/pull/9567.md)

Version: 7

Head commit: b4e082c2d33a1197543f2c9425f1e0b82ddacbbe

Base commit: 00bf96a629144b9d6e92112ade8c6ce35e6f7f3e

Comparison base: 00bf96a629144b9d6e92112ade8c6ce35e6f7f3e

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## \.gitattributes

Status: modified; +3; −0

```diff
@@ -1,2 +1,5 @@
 # git autocrlf=true converts LF to CRLF on Windows, causing issues with oxfmt
 * text=auto eol=lf
+# Batch files need CRLF for cmd.exe to parse them reliably.
+*.cmd text eol=crlf
+*.bat text eol=crlf
```

## apps/server/src/provider/Layers/CodexCollabRuntime.integration.test.ts

Status: modified; +6; −1

```diff
@@ -23,6 +23,7 @@
 
 import wireFixture from "../testFixtures/codexMultiAgentWire.json" with { type: "json" };
 import { makeCodexSessionRuntime } from "./CodexSessionRuntime.ts";
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
 
 const ROOT = wireFixture.rootThreadId;
 const [CHILD_A, CHILD_B] = wireFixture.childThreadIds as [string, string];
@@ -157,7 +158,11 @@
 }
 
 const scriptPath = NodePath.join(import.meta.dirname, "../testFixtures/.collab-script.json");
+// Windows cannot run the shebang wrapper; the .cmd sibling does the same job.
+const peerPath = NodePath.join(
+  import.meta.dirname,
+  `../testFixtures/codexCollabMockPeer.${HostProcessPlatform.defaultValue() === "win32" ? "cmd" : "sh"}`,
+);
-const peerPath = NodePath.join(import.meta.dirname, "../testFixtures/codexCollabMockPeer.sh");
 
 describe("CodexSessionRuntime collab integration", () => {
   it.effect("looks up child model metadata once after activity registration", () =>
```

## apps/server/src/provider/Layers/CursorAdapter.test.ts

Status: modified; +24; −31

```diff
@@ -30,6 +30,8 @@
 import { ServerSettingsService } from "../../serverSettings.ts";
 import type { CursorAdapterShape } from "../Services/CursorAdapter.ts";
 import { makeCursorAdapter } from "./CursorAdapter.ts";
+import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
 const decodeCursorSettings = Schema.decodeSync(CursorSettings);
 
 // Test-local service tag so the rest of the file can keep using `yield* CursorAdapter`.
@@ -39,26 +41,25 @@
 
 const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
 const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");
+// Stopping a session kills the agent with SIGTERM; Windows terminates the
+// process instead, so the mock never sees a signal to log.
-const mockAgentCommand = "node";
-const mockAgentArgs = [mockAgentPath] as const;
-
+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
 async function makeMockAgentWrapper(
   extraEnv?: Record<string, string>,
   options?: { initialDelaySeconds?: number },
 ) {
   const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-mock-"));
-  const wrapperPath = NodePath.join(dir, "fake-agent.sh");
-  const envExports = Object.entries(extraEnv ?? {})
-    .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
-    .join("\n");
-  const script = `#!/bin/sh
-${envExports}
+  return writeFakeCli({
+    directory: dir,
+    name: "fake-agent",
+    env: extraEnv ?? {},
-${options?.initialDelaySeconds ? `sleep ${JSON.stringify(String(options.initialDelaySeconds))}` : ""}
-exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
-`;
+    source: execScriptSource({
+      scriptPath: mockAgentPath,
+      ...(options?.initialDelaySeconds === undefined
+        ? {}
+        : { delayMs: Math.round(options.initialDelaySeconds * 1000) }),
+    }),
+  });
-  await NodeFSP.writeFile(wrapperPath, script, "utf8");
-  await NodeFSP.chmod(wrapperPath, 0o755);
-  return wrapperPath;
 }
 
 async function makeProbeWrapper(
@@ -67,20 +68,12 @@
   extraEnv?: Record<string, string>,
 ) {
   const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "cursor-acp-probe-"));
-  const wrapperPath = NodePath.join(dir, "fake-agent.sh");
-  const envExports = Object.entries(extraEnv ?? {})
-    .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
-    .join("\n");
+  return writeFakeCli({
+    directory: dir,
+    name: "fake-agent",
+    env: { T3_ACP_REQUEST_LOG_PATH: requestLogPath, ...extraEnv },
+    source: execScriptSource({ scriptPath: mockAgentPath, argvLogPath }),
+  });
-  const script = `#!/bin/sh
-printf '%s\t' "$@" >> ${JSON.stringify(argvLogPath)}
-printf '\n' >> ${JSON.stringify(argvLogPath)}
-export T3_ACP_REQUEST_LOG_PATH=${JSON.stringify(requestLogPath)}
-${envExports}
-exec ${JSON.stringify(mockAgentCommand)} ${mockAgentArgs.map((arg) => JSON.stringify(arg)).join(" ")} "$@"
-`;
-  await NodeFSP.writeFile(wrapperPath, script, "utf8");
-  await NodeFSP.chmod(wrapperPath, 0o755);
-  return wrapperPath;
 }
 
 async function readArgvLog(filePath: string) {
@@ -390,7 +383,7 @@
     }),
   );
 
-  it.effect("closes the ACP child process when a session stops", () =>
+  it.effect.skipIf(windowsHost)("closes the ACP child process when a session stops", () =>
     Effect.gen(function* () {
       const adapter = yield* CursorAdapter;
       const settings = yield* ServerSettingsService;
@@ -422,7 +415,7 @@
     }),
   );
 
+  it.effect.skipIf(windowsHost)(
-  it.effect(
     "serializes concurrent startSession calls for the same thread and closes the replaced ACP session",
     () =>
       Effect.gen(function* () {
```

## apps/server/src/provider/Layers/CursorProvider.test.ts

Status: modified; +25; −28

```diff
@@ -30,6 +30,8 @@
   probeCursorSkills,
   rewriteCursorSkillMentions,
 } from "../Drivers/CursorSkills.ts";
+import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
 
 const runNode = <A, E>(
   effect: Effect.Effect<
@@ -39,6 +41,10 @@
   >,
 ): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer)));
 
+// Closing the probe kills the agent with SIGTERM; Windows terminates the
+// process instead, so the mock never sees a signal to log.
+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
+
 const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () {
   const path = yield* Path.Path;
   return yield* path.fromFileUrl(new URL("../../../scripts/acp-mock-agent.ts", import.meta.url));
@@ -73,47 +79,38 @@
   extraEnv?: Record<string, string>,
 ) {
   const fileSystem = yield* FileSystem.FileSystem;
-  const path = yield* Path.Path;
   const mockAgentPath = yield* resolveMockAgentPath();
   const dir = yield* fileSystem.makeTempDirectory({
     directory: NodeOS.tmpdir(),
     prefix: "cursor-provider-mock-",
   });
+  return writeFakeCli({
+    directory: dir,
+    name: "fake-agent",
+    env: extraEnv ?? {},
+    source: execScriptSource({ scriptPath: mockAgentPath }),
+  });
-  const wrapperPath = path.join(dir, "fake-agent.sh");
-  const mockAgentCommand = ["node", mockAgentPath].map((arg) => JSON.stringify(arg)).join(" ");
-  const envExports = Object.entries(extraEnv ?? {})
-    .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
-    .join("\n");
-  const script = `#!/bin/sh
-${envExports}
-exec ${mockAgentCommand} "$@"
-`;
-  yield* fileSystem.writeFileString(wrapperPath, script);
-  yield* fileSystem.chmod(wrapperPath, 0o755);
-  return wrapperPath;
 });
 
 const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper")(function* () {
   const fileSystem = yield* FileSystem.FileSystem;
-  const path = yield* Path.Path;
   const mockAgentPath = yield* resolveMockAgentPath();
   const dir = yield* fileSystem.makeTempDirectory({
     directory: NodeOS.tmpdir(),
     prefix: "cursor-provider-about-mock-",
   });
+  return writeFakeCli({
-  const wrapperPath = path.join(dir, "fake-agent.sh");
-  const mockAgentCommand = ["node", mockAgentPath].map((arg) => JSON.stringify(arg)).join(" ");
-  const script = `#!/bin/sh
-if [ "$1" = "about" ]; then
-  printf 'CLI Version         2026.04.09-f2b0fcd\\n'
-  printf 'User Email          cursor@example.com\\n'
-  exit 0
-fi
-exec ${mockAgentCommand} "$@"
-`;
-  yield* fileSystem.writeFileString(wrapperPath, script);
-  yield* fileSystem.chmod(wrapperPath, 0o755);
-  return wrapperPath;
+    directory: dir,
+    name: "fake-agent",
+    source: [
+      'if (process.argv[2] === "about") {',
+      '  process.stdout.write("CLI Version         2026.04.09-f2b0fcd\\n");',
+      '  process.stdout.write("User Email          cursor@example.com\\n");',
+      "  process.exit(0);",
+      "}",
+      execScriptSource({ scriptPath: mockAgentPath }),
+    ].join("\n"),
+  });
 });
 
 const waitForFileContent = Effect.fn("waitForFileContent")(function* (
@@ -593,7 +590,7 @@
     ]);
   });
 
+  it.skipIf(windowsHost)("closes the ACP probe runtime after discovery completes", async () => {
-  it("closes the ACP probe runtime after discovery completes", async () => {
     const { exitLogPath, wrapperPath } = await runNode(
       makeExitLogFixture("cursor-provider-exit-log-"),
     );
```

## apps/server/src/provider/Layers/GrokAdapter.test.ts

Status: modified; +12; −13

```diff
@@ -33,25 +33,24 @@
   nextGrokPlanModeActive,
   selectGrokPermissionOptionId,
 } from "./GrokAdapter.ts";
+import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
 const decodeGrokSettings = Schema.decodeSync(GrokSettings);
 
 const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
 const mockAgentPath = NodePath.join(__dirname, "../../../scripts/acp-mock-agent.ts");
+// Stopping a session kills the agent with SIGTERM; Windows terminates the
+// process instead, so the mock never sees a signal to log.
-const mockAgentCommand = process.execPath;
+const windowsHost = HostProcessPlatform.defaultValue() === "win32";
 
 async function makeMockGrokWrapper(extraEnv?: Record<string, string>) {
   const dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "grok-acp-mock-"));
+  return writeFakeCli({
+    directory: dir,
+    name: "fake-grok",
-  const wrapperPath = NodePath.join(dir, "fake-grok.sh");
+    env: extraEnv ?? {},
+    source: execScriptSource({ scriptPath: mockAgentPath }),
+  });
-  const envExports = Object.entries(extraEnv ?? {})
-    .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
-    .join("\n");
-  const script = `#!/bin/sh
-${envExports}
-exec ${JSON.stringify(mockAgentCommand)} ${JSON.stringify(mockAgentPath)} "$@"
-`;
-  await NodeFSP.writeFile(wrapperPath, script, "utf8");
-  await NodeFSP.chmod(wrapperPath, 0o755);
-  return wrapperPath;
 }
 
 function waitForFileContent(
@@ -340,7 +339,7 @@
     }),
   );
 
+  it.effect.skipIf(windowsHost)("closes the ACP child process when a session stops", () =>
-  it.effect("closes the ACP child process when a session stops", () =>
     Effect.gen(function* () {
       const threadId = ThreadId.make("grok-stop-session-close");
       const tempDir = yield* Effect.promise(() =>
```

## apps/server/src/provider/Layers/GrokProvider.test.ts

Status: modified; +29; −32

```diff
@@ -6,7 +6,6 @@
 import { describe, expect, it } from "@effect/vitest";
 import * as Effect from "effect/Effect";
 import * as FileSystem from "effect/FileSystem";
-import * as Path from "effect/Path";
 import * as Schema from "effect/Schema";
 import { GrokSettings } from "@t3tools/contracts";
 
@@ -17,6 +16,7 @@
   checkGrokProviderStatus,
   parseGrokModelsCliOutput,
 } from "./GrokProvider.ts";
+import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts";
 
 const decodeGrokSettings = Schema.decodeSync(GrokSettings);
 const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
@@ -308,14 +308,17 @@
       const snapshot = yield* Effect.scoped(
         Effect.gen(function* () {
           const fs = yield* FileSystem.FileSystem;
-          const path = yield* Path.Path;
           const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-grok-version-" });
-          const grokPath = path.join(dir, "grok");
-          yield* fs.writeFileString(
-            grokPath,
+          const grokPath = writeFakeCli({
+            directory: dir,
+            name: "grok",
+            source: [
+              // @effect-diagnostics-next-line preferSchemaOverJson:off
+              `process.stderr.write(${JSON.stringify(`${secretStderr}\n`)});`,
+              "process.exit(2);",
+              "",
+            ].join("\n"),
+          });
-            ["#!/bin/sh", `printf "%s\\n" "${secretStderr}" >&2`, "exit 2", ""].join("\n"),
-          );
-          yield* fs.chmod(grokPath, 0o755);
 
           return yield* checkGrokProviderStatus(
             decodeGrokSettings({ enabled: true, binaryPath: grokPath }),
@@ -331,37 +334,31 @@
     }),
   );
 
-  // Single-quotes a path for /bin/sh. Temp dirs and execPath never contain quotes.
-  const shellQuote = (value: string) => `'${value.replaceAll("'", `'\\''`)}'`;
-
-  // A shell stand-in for the Grok CLI: `--version` and `models` print canned text,
+  // A stand-in for the Grok CLI: `--version` and `models` print canned text,
   // and `agent stdio` execs the mock ACP agent so `initialize` returns model metadata.
   const writeFakeGrokCli = (input: { readonly modelsOutput: string; readonly acp: boolean }) =>
     Effect.gen(function* () {
       const fs = yield* FileSystem.FileSystem;
-      const path = yield* Path.Path;
       const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-grok-probe-" });
+      const mockAgentPath = NodePath.resolve(__dirname, "../../../scripts/acp-mock-agent.ts");
+      return writeFakeCli({
+        directory: dir,
+        name: "grok",
+        source: [
+          'if (process.argv[2] === "--version") {',
-      const modelsPath = path.join(dir, "models.txt");
-      yield* fs.writeFileString(modelsPath, input.modelsOutput);
-      const grokPath = path.join(dir, "grok");
-      const mockAgentPath = path.resolve(__dirname, "../../../scripts/acp-mock-agent.ts");
-      yield* fs.writeFileString(
-        grokPath,
-        [
-          "#!/bin/sh",
-          'case "$1" in',
-          '  --version) printf "grok 1.0.13\\n"; exit 0;;',
-          `  models) cat ${shellQuote(modelsPath)}; exit 0;;`,
-          input.acp
-            ? `  agent) exec ${shellQuote(process.execPath)} ${shellQuote(mockAgentPath)};;`
-            : "  agent) exit 3;;",
-          "esac",
+          '  process.stdout.write("grok 1.0.13\\n");',
+          "  process.exit(0);",
+          "}",
+          'if (process.argv[2] === "models") {',
+          // @effect-diagnostics-next-line preferSchemaOverJson:off
+          `  process.stdout.write(${JSON.stringify(input.modelsOutput)});`,
+          "  process.exit(0);",
+          "}",
-          "exit 1",
+          'if (process.argv[2] !== "agent") process.exit(1);',
+          ...(input.acp ? [execScriptSource({ scriptPath: mockAgentPath })] : ["process.exit(3);"]),
           "",
         ].join("\n"),
+      });
-      );
-      yield* fs.chmod(grokPath, 0o755);
-      return grokPath;
     });
 
   it.effect("reports ready with ACP-discovered models when logged in", () =>
```

## apps/server/src/provider/testFixtures/codexCollabMockPeer.cmd

Status: added; +8; −0

```diff
@@ -1,0 +1,8 @@
+@echo off
+rem Wrapper so CodexSessionRuntime can spawn the mock peer on Windows: the
+rem runtime always passes "app-server" as the first argument; drop it and
+rem run the .mjs peer with node. "shift /1" leaves %0 alone so %~dp0 still
+rem names this file's directory.
+shift /1
+node "%~dp0codexCollabMockPeer.mjs" %1 %2 %3 %4 %5 %6 %7 %8 %9
+exit /b %ERRORLEVEL%
```

## apps/server/src/testUtils/fakeCli.ts

Status: added; +106; −0

```diff
@@ -1,0 +1,106 @@
+// @effect-diagnostics nodeBuiltinImport:off preferSchemaOverJson:off - synchronous fixture writer used from plain and Effect tests alike.
+import * as NodeFS from "node:fs";
+import * as NodePath from "node:path";
+
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
+
+export interface FakeCliOptions {
+  /** Directory the launcher and its stub are written into. */
+  readonly directory: string;
+  /** Command name as callers spawn it, e.g. `codex`. */
+  readonly name: string;
+  /** ES module source the launcher runs with `node`. `process.argv.slice(2)` carries the CLI args. */
+  readonly source: string;
+  /** Environment set for the stub before it runs, on top of the inherited environment. */
+  readonly env?: Readonly<Record<string, string>>;
+  /**
+   * Platform the launcher is shaped for. Effect callers should pass the value
+   * they read from `HostProcessPlatform` so an injected override is honoured;
+   * defaults to the real host for plain tests.
+   */
+  readonly platform?: NodeJS.Platform;
+}
+
+/**
+ * Writes a fake CLI whose behaviour lives in a Node stub. On posix the
+ * launcher is a `#!/bin/sh` script; on Windows it is a `.cmd` shim, since a
+ * shebang file is not executable there and `resolveSpawnCommand` routes
+ * `.cmd` through a shell. Returns the launcher path to hand to the code under
+ * test, which on Windows carries the `.cmd` extension.
+ */
+export function writeFakeCli(options: FakeCliOptions): string {
+  NodeFS.mkdirSync(options.directory, { recursive: true });
+  const stubPath = NodePath.join(options.directory, `${options.name}-stub.mjs`);
+  // The environment rides in a JSON sidecar the stub applies to itself, since
+  // neither sh nor cmd.exe can quote every value (newlines, quotes, `%`) safely.
+  const envPath = NodePath.join(options.directory, `${options.name}-env.json`);
+  NodeFS.writeFileSync(envPath, JSON.stringify(options.env ?? {}), "utf8");
+  NodeFS.writeFileSync(
+    stubPath,
+    [
+      'import { readFileSync as readFakeCliEnv } from "node:fs";',
+      `Object.assign(process.env, JSON.parse(readFakeCliEnv(${JSON.stringify(envPath)}, "utf8")));`,
+      options.source,
+    ].join("\n"),
+    "utf8",
+  );
+
+  if ((options.platform ?? HostProcessPlatform.defaultValue()) === "win32") {
+    const launcherPath = NodePath.join(options.directory, `${options.name}.cmd`);
+    NodeFS.writeFileSync(
+      launcherPath,
+      ["@echo off", `node "%~dp0${options.name}-stub.mjs" %*`, "exit /b %ERRORLEVEL%", ""].join(
+        "\r\n",
+      ),
+      "utf8",
+    );
+    return launcherPath;
+  }
+
+  const launcherPath = NodePath.join(options.directory, options.name);
+  NodeFS.writeFileSync(
+    launcherPath,
+    ["#!/bin/sh", `exec node "$(dirname "$0")/${options.name}-stub.mjs" "$@"`, ""].join("\n"),
+    "utf8",
+  );
+  NodeFS.chmodSync(launcherPath, 0o755);
+  return launcherPath;
+}
+
+/**
+ * Stub source that becomes `scriptPath`: after optionally requiring a leading
+ * argv prefix, it imports the script into its own process so the stub is the
+ * agent. Nothing sits between the code under test and the mock, so a kill or
+ * a closed stdin reaches it directly and its exit log is faithful on every
+ * host. The script must not read `process.argv`; the mock agents are driven
+ * by environment and stdin.
+ */
+export function execScriptSource(options: {
+  readonly scriptPath: string;
+  readonly expectedArgs?: ReadonlyArray<string>;
+  /** Tab-separated argv appended here per invocation, for launch-flag assertions. */
+  readonly argvLogPath?: string;
+  /** Wait before handing over, for tests that race a slow startup. */
+  readonly delayMs?: number;
+}): string {
+  return [
+    'import { appendFileSync } from "node:fs";',
+    'import { pathToFileURL } from "node:url";',
+    "const args = process.argv.slice(2);",
+    ...(options.argvLogPath === undefined
+      ? []
+      : [
+          `appendFileSync(${JSON.stringify(options.argvLogPath)}, args.join(${JSON.stringify("\t")}) + ${JSON.stringify("\n")});`,
+        ]),
+    ...(options.delayMs === undefined
+      ? []
+      : [`Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ${options.delayMs});`]),
+    `const expected = ${JSON.stringify(options.expectedArgs ?? [])};`,
+    "if (expected.some((value, index) => args[index] !== value)) {",
+    '  process.stderr.write(`unexpected args: ${args.join(" ")}\\n`);',
+    "  process.exit(11);",
+    "}",
+    `await import(pathToFileURL(${JSON.stringify(options.scriptPath)}).href);`,
+    "",
+  ].join("\n");
+}
```

## apps/server/src/textGeneration/ClaudeTextGeneration.test.ts

Status: modified; +12; −30

```diff
@@ -1,7 +1,7 @@
 import * as NodeServices from "@effect/platform-node/NodeServices";
 import { it } from "@effect/vitest";
 import { ClaudeSettings, ProviderInstanceId } from "@t3tools/contracts";
-import { isHostWindows } from "@t3tools/shared/hostProcess";
+import { HostProcessPlatform, isHostWindows } from "@t3tools/shared/hostProcess";
 import { createModelSelection } from "@t3tools/shared/model";
 import * as Effect from "effect/Effect";
 import * as FileSystem from "effect/FileSystem";
@@ -21,27 +21,26 @@
 import * as TextGeneration from "./TextGeneration.ts";
 import { sanitizeThreadTitle } from "./TextGenerationUtils.ts";
 import { makeClaudeTextGeneration } from "./ClaudeTextGeneration.ts";
+import { writeFakeCli } from "../testUtils/fakeCli.ts";
 const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings);
 
 const ClaudeTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
   prefix: "t3code-claude-text-generation-test-",
 }).pipe(Layer.provideMerge(NodeServices.layer));
 
+// The stub behaviour lives in Node so the same implementation runs on Windows,
+// where a shebang file is not executable and would fall through to the real
+// Claude CLI on PATH; `writeFakeCli` picks the launcher shape per host.
 function makeFakeClaudeBinary(dir: string) {
   return Effect.gen(function* () {
-    const fs = yield* FileSystem.FileSystem;
     const path = yield* Path.Path;
+    const platform = yield* HostProcessPlatform;
-    const isWindows = yield* isHostWindows;
     const binDir = path.join(dir, "bin");
+    writeFakeCli({
+      directory: binDir,
-    const stubPath = path.join(binDir, "claude-stub.mjs");
-    yield* fs.makeDirectory(binDir, { recursive: true });
-
-    // The stub behaviour lives in Node rather than a `#!/bin/sh` script so the
-    // same implementation is usable on Windows, where a shebang file is not
-    // executable and would fall through to the real Claude CLI on PATH.
+      name: "claude",
-    yield* fs.writeFileString(
-      stubPath,
-      [
+      platform,
+      source: [
         'const args = process.argv.slice(2).join(" ");',
         "",
         "function fail(message, code) {",
@@ -87,24 +86,7 @@
         "process.exitCode = Number(process.env.T3_FAKE_CLAUDE_EXIT_CODE ?? 0);",
         "",
       ].join("\n"),
-    );
-
+    });
-    if (isWindows) {
-      // Windows resolves executables through PATHEXT, so the entry point has to
-      // carry a real extension. `resolveSpawnCommand` spawns `.cmd` via a shell.
-      yield* fs.writeFileString(
-        path.join(binDir, "claude.cmd"),
-        ["@echo off", 'node "%~dp0claude-stub.mjs" %*', "exit /b %ERRORLEVEL%", ""].join("\r\n"),
-      );
-    } else {
-      const claudePath = path.join(binDir, "claude");
-      yield* fs.writeFileString(
-        claudePath,
-        ["#!/bin/sh", 'exec node "$(dirname "$0")/claude-stub.mjs" "$@"', ""].join("\n"),
-      );
-      yield* fs.chmod(claudePath, 0o755);
-    }
-
     return binDir;
   });
 }
```

## apps/server/src/textGeneration/CodexTextGeneration.test.ts

Status: modified; +99; −156

```diff
@@ -14,6 +14,7 @@
 import * as ServerConfig from "../config.ts";
 import * as TextGeneration from "./TextGeneration.ts";
 import { makeCodexTextGeneration } from "./CodexTextGeneration.ts";
+import { writeFakeCli } from "../testUtils/fakeCli.ts";
 const decodeCodexSettings = Schema.decodeSync(CodexSettings);
 
 const DEFAULT_TEST_MODEL_SELECTION = createModelSelection(
@@ -25,170 +26,112 @@
   prefix: "t3code-codex-text-generation-test-",
 }).pipe(Layer.provideMerge(NodeServices.layer));
 
-function makeFakeCodexBinary(
-  dir: string,
-  input: {
-    output: string;
-    exitCode?: number;
-    stderr?: string;
-    requireImage?: boolean;
-    requireServiceTier?: string;
-    requireReasoningEffort?: string;
-    forbidReasoningEffort?: boolean;
-    requireArg?: string;
-    forbidArg?: string;
-    stdinMustContain?: string;
-    stdinMustNotContain?: string;
-  },
-) {
+interface FakeCodexInput {
+  output: string;
+  exitCode?: number;
+  stderr?: string;
+  requireImage?: boolean;
+  requireServiceTier?: string;
+  requireReasoningEffort?: string;
+  forbidReasoningEffort?: boolean;
+  requireArg?: string;
+  forbidArg?: string;
+  stdinMustContain?: string;
+  stdinMustNotContain?: string;
+}
+
+// The stub walks argv the way the shell script it replaced did: `--image`,
+// `--config key=value`, and `--output-last-message <path>` are consumed, the
+// prompt arrives on stdin, and each check exits with its own code so a
+// failing test names the assertion that tripped.
+function makeFakeCodexBinary(dir: string, input: FakeCodexInput) {
+  const check = JSON.stringify({
+    requireImage: input.requireImage ?? false,
+    requireServiceTier: input.requireServiceTier ?? null,
+    requireReasoningEffort: input.requireReasoningEffort ?? null,
+    forbidReasoningEffort: input.forbidReasoningEffort ?? false,
+    requireArg: input.requireArg ?? null,
+    forbidArg: input.forbidArg ?? null,
+    stdinMustContain: input.stdinMustContain ?? null,
+    stdinMustNotContain: input.stdinMustNotContain ?? null,
+    stderr: input.stderr ?? null,
+    output: input.output,
+    exitCode: input.exitCode ?? 0,
+  });
   return Effect.gen(function* () {
-    const fs = yield* FileSystem.FileSystem;
     const path = yield* Path.Path;
-    const binDir = path.join(dir, "bin");
-    const codexPath = path.join(binDir, "codex");
-    yield* fs.makeDirectory(binDir, { recursive: true });
-
-    yield* fs.writeFileString(
-      codexPath,
-      [
-        "#!/bin/sh",
-        'original_args="$*"',
-        'output_path=""',
-        'seen_image="0"',
-        'seen_service_tier=""',
-        'seen_reasoning_effort=""',
-        "while [ $# -gt 0 ]; do",
+    return writeFakeCli({
+      directory: path.join(dir, "bin"),
+      name: "codex",
+      source: [
+        'import * as NodeFS from "node:fs";',
+        `const check = ${check};`,
+        "const args = process.argv.slice(2);",
+        'const originalArgs = ` ${args.join(" ")} `;',
+        "let outputPath = null;",
+        "let seenImage = false;",
+        'let seenServiceTier = "";',
+        'let seenReasoningEffort = "";',
+        "for (let index = 0; index < args.length; index += 1) {",
+        '  if (args[index] === "--image") {',
+        "    index += 1;",
+        "    if (args[index]) seenImage = true;",
+        '  } else if (args[index] === "--config") {',
+        "    index += 1;",
+        '    const value = args[index] ?? "";',
+        '    if (value.startsWith("service_tier=")) seenServiceTier = value;',
+        '    if (value.startsWith("model_reasoning_effort=")) seenReasoningEffort = value;',
+        '  } else if (args[index] === "--output-last-message") {',
+        "    index += 1;",
+        "    outputPath = args[index] ?? null;",
+        "  }",
+        "}",
+        "const chunks = [];",
+        "for await (const chunk of process.stdin) chunks.push(chunk);",
+        'const stdinContent = Buffer.concat(chunks).toString("utf8");',
+        "function fail(message, code) {",
+        '  process.stderr.write(message + "\\n");',
+        "  process.exit(code);",
+        "}",
+        "if (check.requireArg !== null && !originalArgs.includes(` ${check.requireArg} `)) {",
+        '  fail("missing arg: " + check.requireArg, 8);',
+        "}",
+        "if (check.forbidArg !== null && originalArgs.includes(` ${check.forbidArg} `)) {",
+        '  fail("forbidden arg: " + check.forbidArg, 9);',
+        "}",
+        'if (check.requireImage && !seenImage) fail("missing --image input", 2);',
+        "if (",
+        "  check.requireServiceTier !== null &&",
+        '  seenServiceTier !== `service_tier="${check.requireServiceTier}"`',
+        ") {",
+        '  fail("unexpected service tier config: " + seenServiceTier, 5);',
+        "}",
+        "if (",
+        "  check.requireReasoningEffort !== null &&",
+        '  seenReasoningEffort !== `model_reasoning_effort="${check.requireReasoningEffort}"`',
+        ") {",
+        '  fail("unexpected reasoning effort config: " + seenReasoningEffort, 6);',
+        "}",
+        "if (check.forbidReasoningEffort && seenReasoningEffort.length > 0) {",
+        '  fail("reasoning effort config should be omitted: " + seenReasoningEffort, 7);',
+        "}",
+        "if (check.stdinMustContain !== null && !stdinContent.includes(check.stdinMustContain)) {",
+        '  fail("stdin missing expected content", 3);',
+        "}",
+        "if (check.stdinMustNotContain !== null && stdinContent.includes(check.stdinMustNotContain)) {",
+        '  fail("stdin contained forbidden content", 4);',
+        "}",
+        'if (check.stderr !== null) process.stderr.write(check.stderr + "\\n");',
+        'if (outputPath !== null) NodeFS.writeFileSync(outputPath, check.output + "\\n");',
+        "process.exitCode = check.exitCode;",
-        '  if [ "$1" = "--image" ]; then',
-        "    shift",
-        '    if [ -n "$1" ]; then',
-        '      seen_image="1"',
-        "    fi",
-        "    shift",
-        "    continue",
-        "  fi",
-        '  if [ "$1" = "--config" ]; then',
-        "    shift",
-        '    case "$1" in',
-        "      service_tier=*)",
-        '        seen_service_tier="$1"',
-        "        ;;",
-        "    esac",
-        '    case "$1" in',
-        "      model_reasoning_effort=*)",
-        '        seen_reasoning_effort="$1"',
-        "        ;;",
-        "    esac",
-        "    shift",
-        "    continue",
-        "  fi",
-        '  if [ "$1" = "--output-last-message" ]; then',
-        "    shift",
-        '    output_path="$1"',
-        "    shift",
-        "    continue",
-        "  fi",
-        "  shift",
-        "done",
-        'stdin_content="$(cat)"',
-        ...(input.requireArg !== undefined
-          ? [
-              `case " $original_args " in *" ${input.requireArg} "*) ;; *)`,
-              `  printf "%s\\n" "missing arg: ${input.requireArg}" >&2`,
-              `  exit 8`,
-              "esac",
-            ]
-          : []),
-        ...(input.forbidArg !== undefined
-          ? [
-              `case " $original_args " in *" ${input.forbidArg} "*)`,
-              `  printf "%s\\n" "forbidden arg: ${input.forbidArg}" >&2`,
-              `  exit 9`,
-              "esac",
-            ]
-          : []),
-        ...(input.requireImage
-          ? [
-              'if [ "$seen_image" != "1" ]; then',
-              '  printf "%s\\n" "missing --image input" >&2',
-              `  exit 2`,
-              "fi",
-            ]
-          : []),
-        ...(input.requireServiceTier
-          ? [
-              `if [ "$seen_service_tier" != "service_tier=\\"${input.requireServiceTier}\\"" ]; then`,
-              '  printf "%s\\n" "unexpected service tier config: $seen_service_tier" >&2',
-              `  exit 5`,
-              "fi",
-            ]
-          : []),
-        ...(input.requireReasoningEffort !== undefined
-          ? [
-              `if [ "$seen_reasoning_effort" != "model_reasoning_effort=\\"${input.requireReasoningEffort}\\"" ]; then`,
-              '  printf "%s\\n" "unexpected reasoning effort config: $seen_reasoning_effort" >&2',
-              `  exit 6`,
-              "fi",
-            ]
-          : []),
-        ...(input.forbidReasoningEffort
-          ? [
-              'if [ -n "$seen_reasoning_effort" ]; then',
-              '  printf "%s\\n" "reasoning effort config should be omitted: $seen_reasoning_effort" >&2',
-              `  exit 7`,
-              "fi",
-            ]
-          : []),
-        ...(input.stdinMustContain !== undefined
-          ? [
-              // @effect-diagnostics-next-line preferSchemaOverJson:off
-              `if ! printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustContain)} >/dev/null; then`,
-              '  printf "%s\\n" "stdin missing expected content" >&2',
-              `  exit 3`,
-              "fi",
-            ]
-          : []),
-        ...(input.stdinMustNotContain !== undefined
-          ? [
-              // @effect-diagnostics-next-line preferSchemaOverJson:off
-              `if printf "%s" "$stdin_content" | grep -F -- ${JSON.stringify(input.stdinMustNotContain)} >/dev/null; then`,
-              '  printf "%s\\n" "stdin contained forbidden content" >&2',
-              `  exit 4`,
-              "fi",
-            ]
-          : []),
-        ...(input.stderr !== undefined
-          ? [
-              // @effect-diagnostics-next-line preferSchemaOverJson:off
-              `printf "%s\\n" ${JSON.stringify(input.stderr)} >&2`,
-            ]
-          : []),
-        'if [ -n "$output_path" ]; then',
-        "  cat > \"$output_path\" <<'__T3CODE_FAKE_CODEX_OUTPUT__'",
-        input.output,
-        "__T3CODE_FAKE_CODEX_OUTPUT__",
-        "fi",
-        `exit ${input.exitCode ?? 0}`,
         "",
       ].join("\n"),
-    );
+    });
-    yield* fs.chmod(codexPath, 0o755);
-    return codexPath;
   });
 }
 
 function withFakeCodexEnv<A, E, R>(
-  input: {
-    output: string;
-    exitCode?: number;
-    stderr?: string;
-    requireImage?: boolean;
+  input: FakeCodexInput & {
-    requireServiceTier?: string;
-    requireReasoningEffort?: string;
-    forbidReasoningEffort?: boolean;
-    requireArg?: string;
-    forbidArg?: string;
-    stdinMustContain?: string;
-    stdinMustNotContain?: string;
     launchArgs?: string;
     environment?: NodeJS.ProcessEnv;
   },
```

## apps/server/src/textGeneration/CursorTextGeneration.test.ts

Status: modified; +49; −56

```diff
@@ -11,6 +11,7 @@
 import * as Exit from "effect/Exit";
 import * as Layer from "effect/Layer";
 import * as Schema from "effect/Schema";
+import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
 import { createModelSelection } from "@t3tools/shared/model";
 import { expect } from "vite-plus/test";
 
@@ -19,39 +20,26 @@
 import * as ServerConfig from "../config.ts";
 import * as TextGeneration from "./TextGeneration.ts";
 import { makeCursorTextGeneration } from "./CursorTextGeneration.ts";
+import { execScriptSource, writeFakeCli } from "../testUtils/fakeCli.ts";
 const decodeCursorSettings = Schema.decodeSync(CursorSettings);
 
 const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
 const mockAgentPath = NodePath.join(__dirname, "../../scripts/acp-mock-agent.ts");
 
-function shellSingleQuote(value: string): string {
-  return `'${value.replaceAll("'", `'"'"'`)}'`;
-}
-
 const CursorTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
   prefix: "t3code-cursor-text-generation-test-",
 }).pipe(Layer.provideMerge(NodeServices.layer));
 
 function makeAcpAgentWrapper(dir: string, env: Record<string, string>): string {
+  return writeFakeCli({
+    directory: NodePath.join(dir, "bin"),
+    name: "agent",
+    env,
+    source: execScriptSource({
+      scriptPath: mockAgentPath,
+      expectedArgs: ["acp"],
+    }),
-  const binDir = NodePath.join(dir, "bin");
-  const agentPath = NodePath.join(binDir, "agent");
-  NodeFS.mkdirSync(binDir, { recursive: true });
+  });
-  NodeFS.writeFileSync(
-    agentPath,
-    [
-      "#!/bin/sh",
-      ...Object.entries(env).map(([key, value]) => `export ${key}=${shellSingleQuote(value)}`),
-      'if [ "$1" != "acp" ]; then',
-      '  printf "%s\\n" "unexpected args: $*" >&2',
-      "  exit 11",
-      "fi",
-      `exec node ${JSON.stringify(mockAgentPath)}`,
-      "",
-    ].join("\n"),
-    "utf8",
-  );
-  NodeFS.chmodSync(agentPath, 0o755);
-  return agentPath;
 }
 
 function withFakeAcpAgent<A, E, R>(
@@ -236,41 +224,46 @@
     ),
   );
 
-  it.effect("closes the ACP child process after text generation completes", () => {
-    const exitLogDir = NodeFS.mkdtempSync(
-      NodePath.join(NodeOS.tmpdir(), "t3code-cursor-text-exit-log-"),
-    );
+  // Closing the runtime on Windows is taskkill /F, which never lets the mock
-    const exitLogPath = NodePath.join(exitLogDir, "exit.log");
+  // agent reach its exit handler, so there is no exit log to assert on.
+  it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")(
+    "closes the ACP child process after text generation completes",
+    () => {
+      const exitLogDir = NodeFS.mkdtempSync(
+        NodePath.join(NodeOS.tmpdir(), "t3code-cursor-text-exit-log-"),
+      );
+      const exitLogPath = NodePath.join(exitLogDir, "exit.log");
 
+      return withFakeAcpAgent(
+        {
+          T3_ACP_EXIT_LOG_PATH: exitLogPath,
+          T3_ACP_PROMPT_RESPONSE_TEXT: JSON.stringify({
+            subject: "Close runtime after generation",
-    return withFakeAcpAgent(
-      {
-        T3_ACP_EXIT_LOG_PATH: exitLogPath,
-        T3_ACP_PROMPT_RESPONSE_TEXT: JSON.stringify({
-          subject: "Close runtime after generation",
-          body: "",
-        }),
-      },
-      (textGeneration) =>
-        Effect.gen(function* () {
-          const generated = yield* textGeneration.generateCommitMessage({
-            cwd: process.cwd(),
-            branch: "feature/cursor-runtime-close",
+            body: "",
+          }),
+        },
+        (textGeneration) =>
-            stagedSummary: "M apps/server/src/textGeneration/CursorTextGeneration.ts",
+          Effect.gen(function* () {
-            stagedPatch:
-              "diff --git a/apps/server/src/textGeneration/CursorTextGeneration.ts b/apps/server/src/textGeneration/CursorTextGeneration.ts",
-            modelSelection: {
-              instanceId: ProviderInstanceId.make("cursor"),
-              model: "composer-2",
-            },
-          });
+            const generated = yield* textGeneration.generateCommitMessage({
+              cwd: process.cwd(),
+              branch: "feature/cursor-runtime-close",
+              stagedSummary: "M apps/server/src/textGeneration/CursorTextGeneration.ts",
+              stagedPatch:
+                "diff --git a/apps/server/src/textGeneration/CursorTextGeneration.ts b/apps/server/src/textGeneration/CursorTextGeneration.ts",
+              modelSelection: {
+                instanceId: ProviderInstanceId.make("cursor"),
+                model: "composer-2",
+              },
+            });
 
+            expect(generated.subject).toBe("Close runtime after generation");
-          expect(generated.subject).toBe("Close runtime after generation");
 
-          const exitLog = yield* waitForFileContent(exitLogPath);
-          expect(exitLog).toContain("exit:0");
+            const exitLog = yield* waitForFileContent(exitLogPath);
+            expect(exitLog).toContain("exit:0");
 
+            NodeFS.rmSync(exitLogDir, { recursive: true, force: true });
+          }),
+      );
-          NodeFS.rmSync(exitLogDir, { recursive: true, force: true });
-        }),
-    );
+    },
+  );
-  });
 });
```

## apps/server/src/textGeneration/GrokTextGeneration.test.ts

Status: modified; +10; −23

```diff
@@ -16,39 +16,26 @@
 import * as ServerConfig from "../config.ts";
 import * as TextGeneration from "./TextGeneration.ts";
 import { makeGrokTextGeneration } from "./GrokTextGeneration.ts";
+import { execScriptSource, writeFakeCli } from "../testUtils/fakeCli.ts";
 const decodeGrokSettings = Schema.decodeSync(GrokSettings);
 
 const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url));
 const mockAgentPath = NodePath.join(__dirname, "../../scripts/acp-mock-agent.ts");
 
-function shellSingleQuote(value: string): string {
-  return `'${value.replaceAll("'", `'"'"'`)}'`;
-}
-
 const GrokTextGenerationTestLayer = ServerConfig.ServerConfig.layerTest(process.cwd(), {
   prefix: "t3code-grok-text-generation-test-",
 }).pipe(Layer.provideMerge(NodeServices.layer));
 
 function makeAcpGrokWrapper(dir: string, env: Record<string, string>): string {
+  return writeFakeCli({
+    directory: NodePath.join(dir, "bin"),
+    name: "grok",
+    env,
-  const binDir = NodePath.join(dir, "bin");
-  const grokPath = NodePath.join(binDir, "grok");
-  NodeFS.mkdirSync(binDir, { recursive: true });
+    source: execScriptSource({
+      scriptPath: mockAgentPath,
+      expectedArgs: ["agent", "stdio"],
+    }),
+  });
-  NodeFS.writeFileSync(
-    grokPath,
-    [
-      "#!/bin/sh",
-      ...Object.entries(env).map(([key, value]) => `export ${key}=${shellSingleQuote(value)}`),
-      'if [ "$1" != "agent" ] || [ "$2" != "stdio" ]; then',
-      '  printf "%s\\n" "unexpected args: $*" >&2',
-      "  exit 11",
-      "fi",
-      `exec ${JSON.stringify(process.execPath)} ${JSON.stringify(mockAgentPath)}`,
-      "",
-    ].join("\n"),
-    "utf8",
-  );
-  NodeFS.chmodSync(grokPath, 0o755);
-  return grokPath;
 }
 
 function withFakeAcpGrok<A, E, R>(
```
