Repository navigation
Two AcpAdapterV2 teardown tests fail on Windows (taskkill one-shot + session poison timeout) #17011
Description
Activity
Note
Grok responding on behalf of Julius.
Thanks for the clean repro on two commits. Checked against
mainatff3a04a8ebaaf6bb74b8b5b996013b82e044c700. Not reproduced on Windows here; everything below is from reading the code.Summary: both look like test-only problems. The tests fake
processGroupPlatform: "win32"on a Linux host, but parts of the teardown path and the fixtures depend on the real host platform. CI only runs Ubuntu (e.g.ci.yml#L233), and the Windows lane is manual-only and notes that the suite doesn't pass on Windows yet, so neither test has run on a real Windows host in CI. Both tests and the teardown code they exercise arrived with #2829.1.
Windows teardown is one-shot explicitly with independent finalizer cleanup— mechanism confirmed in code- The test routes
taskkillto a recording stub and everything else to the real spawner (L12301-L12307), setsprocessGroupPlatform: "win32"(L12319), callsterminateProcessGrouptwice concurrently, then closes the scope and asserts the count is still 1 (L12326-L12331). - The explicit path is one-shot via
Effect.cached(L1734-L1736) and usesoptions.processGroupPlatformto choose taskkill (L1706-L1708). - The scope finalizer is a separate effect that does not go through that cache, and it branches on
HostProcessPlatform(the realprocess.platformby default, hostProcess.ts#L7-L12), not onoptions.processGroupPlatform(L1737-L1752):- On a Windows host it runs
terminateWindowsProcessTreeWithTaskkill(spawner, …), using the same stubbed spawner, so the stub records a secondtaskkill→expected 2 to equal 1. - On a Linux host (CI) it falls through to
signalOwnedProcessGroup("SIGKILL"), which never touches the taskkill stub, so the count stays 1.
- On a Windows host it runs
So the finalizer runs independently of the one-shot guard by design (that's what the test name says), and the second assertion only holds on a non-Windows host. In production on Windows this means one extra
taskkill /T /Fon the same PID at scope close after an explicit teardown, with any failure swallowed and logged (L1749-L1750). That matches the "independent finalizer" intent and isn't a functional bug from what the code shows.2.
poisons the session when hard teardown defects and blocks replacement work— timeout cause is a hypothesisConfirmed from code:
- The test enables
T3_ACP_EMIT_RUNNING_COMMAND_THEN_HANGwith a PID file (L12483, L12486). The mock agent then runsbash -c 'sleep 120 & … printf "%s %s\n" "$$" "$child" > "$1"'viaNodeChildProcess.spawn("bash", …), with noerrorlistener (acp-mock-agent.ts#L1352-L1355, L1375-L1377). - The test's first wait spins on that PID file with no timeout (L12534-L12536). Later waits are unbounded too:
Deferred.await(teardownStarted)(L12554),Fiber.join(interruptFiber)(L12568), and thesession/request_permissionStream.runHead(L12580-L12588). By contrast,waitForProcesses/waitForProcessesToExitare capped at 2s (L277-L289), so a PID mismatch alone would show up as an assertion failure, not a 60s timeout. - On a Windows host the final
Scope.closegoes through the realtaskkill(finalizer above), not the test's injectedwindowsProcessTreeTerminator(L12475-L12481). The test then expects the bash/sleep PIDs it read to be gone, checked withprocess.kill(pid, 0)(L12644-L12650).
Hypothesis (not verified on Windows): the fixture assumes a POSIX
bash/sleepwhose$$/$!are host PIDs. Ifbashon your PATH is missing or is something else (for example WSL'sbash.exe, where a Windows temp path may not resolve), the PID file stays empty and the test spins at L12534 until the 60s timeout. Even with Git Bash, whether its$$/$!match Windows PIDs is unverified, so later assertions might fail too.Precedent: this file already guards several real-process fixture tests to Linux hosts with
if ((yield* HostProcessPlatform) !== "linux") return;(L1722, L1843, L1928).Possible fixes (for whoever picks this up)
- Test 1: make the last assertion host-aware: expect 2 when
HostProcessPlatformiswin32, since the finalizer is meant to be independent. Alternatively, build the runtime with a pinnedHostProcessPlatformand assert on the finalizer path separately. - Test 2: add the same Linux-host guard as the tests above, since it depends on POSIX bash fixtures. Separately, bounding the L12534 wait (e.g.
Effect.timeoutOptionplus a clear assertion message) would turn a missing fixture into a fast, readable failure on any platform. - Longer term, these belong with the effort to get the suite green on the manual Windows lane.
No open PR touches these lines today.
@arslnysuf since you offered to re-run: could you share the output of
where.exe bashandwhere.exe sleepin the shell you runvp testfrom? That should confirm or rule out the PID-file hypothesis for test 2.- The test routes
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.via-triageFiled through npx t3 triageFiled through npx t3 triage
on Oct 8, 2026
Summary
Two teardown tests in
apps/server/src/orchestration-v2/Adapters/AcpAdapterV2.test.tsfail on Windows:AcpAdapterV2 > Windows teardown is one-shot explicitly with independent finalizer cleanup—AssertionError: expected 2 to equal 1at thetaskkillCommands.lengthcheck afterScope.close(finalizer runs taskkill a second time).AcpAdapterV2 > poisons the session when hard teardown defects and blocks replacement work—Error: Test timed out in 60000ms.Repro
From a clean checkout (no local changes):
Both fail deterministically, in isolation as well as in the full-file run.
Environment
vp i)Evidence it predates recent changes
I verified both failures on two clean checkouts with no modifications:
26285ab80(Oct 7, upstreammainhead at the time) — both fail9e5229b47(Oct 5) — both fail identicallyBetween those two commits there are no changes to
apps/server/src/provider/acp/AcpSessionRuntime.ts,apps/server/scripts/acp-mock-agent.ts, or theeffectversion (4.0.1, same patch hash). The test file itself was only touched by #14108 (subagent models, unrelated area) and #16282 (mechanicallayer/layerXyzrenames) in that window. So this looks broken at least since Oct 5, possibly longer.Notes
.github/workflows/ci.yml) runs Ubuntu runners only, which likely explains why a Windows-behavioral failure slipped through: both tests exercise the win32 teardown path (processGroupPlatform: "win32", taskkill-based process group termination) with a real spawned child process.terminateProcessGroupcorrectly produces 1 taskkill, butScope.close(runtimeScope, ...)afterwards bumps it to 2 — the scope finalizer does not observe the one-shot guard on this machine.