From d10a6f15bd3fdb2f7aafeccd52daed11aa2c0b34 Mon Sep 17 00:00:00 2001 From: Sawyer Date: Wed, 9 Sep 2026 23:11:37 -0700 Subject: [PATCH] Harden mid-wait routable test against event-injection race MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The mid-wait case used a fixed 10ms delay before injecting reply events. Under CI load the poll loop sometimes saw only 3 checks before those events settled the run early, failing `toBeGreaterThan(3)` even though the product path was fine. Gate routability behind an explicit flag, wait for ≥3 polls then the retried send, and only then inject replies. --- .../src/one-shot-prompt.test.ts | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/agent-directory/src/one-shot-prompt.test.ts b/packages/agent-directory/src/one-shot-prompt.test.ts index 434e109f5..7f810e0a5 100644 --- a/packages/agent-directory/src/one-shot-prompt.test.ts +++ b/packages/agent-directory/src/one-shot-prompt.test.ts @@ -450,6 +450,7 @@ describe("routable wait on the opening send", () => { const send = createFakeSend({ 1: UNREACHABLE }); const { undeploy, calls: undeployCalls } = createFakeUndeploy(); let routabilityChecks = 0; + let allowRoutable = false; const routabilityAddresses: string[] = []; const deps = { ...createBaseDeps(), @@ -460,7 +461,9 @@ describe("routable wait on the opening send", () => { isRoutable: (address: string) => { routabilityChecks++; routabilityAddresses.push(address); - return routabilityChecks > 3; + // Stays false until the test flips the gate after observing + // several polls — no wall-clock race against event injection. + return allowRoutable; }, deliverWait: { deadlineMs: 5_000, @@ -470,7 +473,33 @@ describe("routable wait on the opening send", () => { } as never; const promise = runOneShotPrompt(deps, INPUT); - await new Promise((r) => setTimeout(r, 10)); + + // Wait until the helper has polled several times while unroutable. + const pollDeadline = Date.now() + 2_000; + while (routabilityChecks < 3) { + if (Date.now() > pollDeadline) { + throw new Error( + `expected ≥3 routability polls before flip; saw ${String(routabilityChecks)}`, + ); + } + await new Promise((r) => setTimeout(r, 1)); + } + const checksBeforeFlip = routabilityChecks; + allowRoutable = true; + + // Wait for the retried send before injecting reply events — the + // event listener is live from provision, so injecting earlier lets + // a reply settle the run while the opening send is still pending + // (and under CI load that race fired after exactly 3 polls). + const sendDeadline = Date.now() + 2_000; + while (send.calls < 2) { + if (Date.now() > sendDeadline) { + throw new Error( + `expected retried send after routable flip; saw ${String(send.calls)} send(s)`, + ); + } + await new Promise((r) => setTimeout(r, 1)); + } const triggerAddress = firstCall(launchCalls).address; fake.emit("agent.event", { @@ -484,7 +513,8 @@ describe("routable wait on the opening send", () => { const result = await promise; expect(result.content).toBe("Hello"); - expect(routabilityChecks).toBeGreaterThan(3); + expect(checksBeforeFlip).toBeGreaterThanOrEqual(3); + expect(routabilityChecks).toBeGreaterThanOrEqual(checksBeforeFlip); // The launched run's address, not some placeholder, is what the // runner consults. expect(routabilityAddresses).toContain(triggerAddress);