From 476cd05705b350e50ed715677e76773638774b62 Mon Sep 17 00:00:00 2001 From: Janghoon Lee <44862514+savagemanage@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:00:41 +0000 Subject: [PATCH] fix(test): scale the ACP subprocess waits for the slow platform too A third Windows shard failed, and this one was not a spawn timeout: `stdin EOF exits cleanly` gave a subprocess five seconds to exit and failed at 6.5s with a bare `TimeoutError`. Windows process teardown is slower, and five seconds was tight enough to flake. Every wait in the ACP test client is on a subprocess round trip, so all three bounds now go through the same scaler as the harness: the request wait, the notification wait's default, and the explicit values callers pass to it. This is the third place a fixed millisecond figure met a slower platform, after the spawn timeout and the ready timeout. The pattern is the point -- a bound written as a literal beside a subprocess is a bound that will eventually be too small on somebody's machine, and `slowPlatform` is where that correction belongs rather than in each number. --- packages/redrob/test/cli/acp/acp-test-client.ts | 9 +++++++-- packages/redrob/test/cli/acp/lifecycle.test.ts | 7 +++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/packages/redrob/test/cli/acp/acp-test-client.ts b/packages/redrob/test/cli/acp/acp-test-client.ts index 1588d344d7..f8bd4ae2af 100644 --- a/packages/redrob/test/cli/acp/acp-test-client.ts +++ b/packages/redrob/test/cli/acp/acp-test-client.ts @@ -2,6 +2,7 @@ import { expect } from "bun:test" import type { SessionConfigOption, SessionConfigSelectOption } from "@agentclientprotocol/sdk" import { Duration, Effect } from "effect" import type { AcpHandle } from "../../lib/cli-process" +import { slowPlatform } from "../../lib/cli-process" type JsonRpcRequest = { readonly jsonrpc: "2.0" @@ -44,15 +45,19 @@ export function createAcpClient(acp: AcpHandle): AcpClient { yield* acp.send(message) while (true) { - const received = yield* acp.receive.pipe(Effect.timeout(Duration.seconds(15))) + const received = yield* acp.receive.pipe(Effect.timeout(Duration.millis(slowPlatform(15_000)))) if (isJsonRpcResponse(received) && received.id === id) return received } }) + /* + The default is scaled for the slow platform, as are the explicit values callers pass -- every wait in + this client is on a subprocess round trip, and Windows is where these bounds flake. + */ const waitForNotification = (method: string, predicate: (params: T) => boolean, timeoutMs = 15_000) => Effect.gen(function* () { while (true) { - const received = yield* acp.receive.pipe(Effect.timeout(Duration.millis(timeoutMs))) + const received = yield* acp.receive.pipe(Effect.timeout(Duration.millis(slowPlatform(timeoutMs)))) if (!isJsonRpcNotification(received)) continue if (received.method === method && predicate(received.params as T)) return received } diff --git a/packages/redrob/test/cli/acp/lifecycle.test.ts b/packages/redrob/test/cli/acp/lifecycle.test.ts index eac2693f1e..de606bb0f0 100644 --- a/packages/redrob/test/cli/acp/lifecycle.test.ts +++ b/packages/redrob/test/cli/acp/lifecycle.test.ts @@ -6,7 +6,7 @@ import type { ResumeSessionResponse, } from "@agentclientprotocol/sdk" import { Duration, Effect } from "effect" -import { cliIt } from "../../lib/cli-process" +import { cliIt, slowPlatform } from "../../lib/cli-process" import { expectOk, selectConfigOption } from "./acp-test-client" import { createAcpClient, initialize, newSession, verifierConfig } from "./helpers" @@ -18,7 +18,10 @@ describe("redrob acp lifecycle subprocess", () => { const acp = yield* redrob.acp() acp.close() - const code = yield* Effect.promise(() => acp.exited).pipe(Effect.timeout(Duration.seconds(5))) + /* Scaled: Windows process teardown is slower, and five seconds was tight enough to flake. */ + const code = yield* Effect.promise(() => acp.exited).pipe( + Effect.timeout(Duration.millis(slowPlatform(5_000))), + ) expect(code).toBe(0) }), 60_000,