Skip to content

test: release the handles a temp project holds before removing it (Windows EPERM) - #1723

Closed
bompus wants to merge 2 commits into
colbymchenry:mainfrom
bompus:fix/windows-temp-dir-handles
Closed

test: release the handles a temp project holds before removing it (Windows EPERM)#1723
bompus wants to merge 2 commits into
colbymchenry:mainfrom
bompus:fix/windows-temp-dir-handles

Conversation

@bompus

@bompus bompus commented Sep 6, 2026

Copy link
Copy Markdown

Fixes #1722.

24 tests across 7 files fail on Windows, every one of them after its assertions have passed, in the fs.rmSync that removes the temp project. Windows will not remove a directory holding an open file; POSIX unlinks one happily, which is why CI is green. Nothing about the indexer is implicated — this is teardown only.

The two causes, fixed separately

fb89ac5 — the graph is never closed. arkts-resolution.test.ts calls CodeGraph.initSync 7 times and close() zero times; three of the four JVM tests in frameworks-integration.test.ts omit the cg.close() their two neighbours already have; the C++/PHP end-to-end tests in resolution.test.ts also open a second DatabaseConnection for a raw edge query and close neither.

Closed in the teardown, not at the end of the test body, so a failing assertion cannot skip the close and turn one red into two. In arkts-resolution.test.ts that means a let cg per describe and a cg?.close() in the existing afterEach; in resolution.test.ts the test's own connection is hoisted out of the try and closed in the finally beside the graph. frameworks-integration.test.ts keeps the end-of-body cg.close() its passing neighbours use, since that is the file's own convention.

476b229 — a killed child still holds its handles. The four mcp-* suites do close the graph and do child.kill('SIGKILL'), then rmSync synchronously in the same tick; the process has been signalled, not reaped. They now await the actual exit. mcp-daemon.test.ts had a fixed setTimeout(50) standing in for this, now replaced by the same await plus the file's own waitProcessExit for the detached daemon it reaps by pid.

Awaiting the tracked child is necessary but not sufficient, and I have the failed run that shows it: with only the awaits, a full suite still failed 2 of 4,240 — a different pair each time — because serve --mcp can leave a detached daemon the suite never tracks, and mcp-daemon's concurrent-launcher test deliberately leaves one alive and asserts it is alive. So the removals also retry, which is what fs.rmSync's maxRetries exists for: 1s in the three proxy suites, 6s in mcp-daemon where the live daemon is the point of the test.

Measured

The diagnosis is an A/B, not a reading: adding cg.close() to arkts-resolution.test.ts's ohpm main entry test turns it green, removing that one line turns it red with EPERM again, same file and command back to back.

before after
Full suite, Windows 3,975 passing / 24 failing 4,017 passing / 0 failing

Run twice in a row after the change, both green, because one green run does not close a race. The 7 affected files alone: 214 passing / 24 failing → 238 passing. tsc --noEmit clean.

Built tree in both arms (tsc + assets + the ui workspace), Windows 11, Node 22. I did not reproduce a Linux run; on POSIX these teardowns cannot fail this way, and the change is inert there apart from the awaits.

These tests build a CodeGraph over a temp project and never close it, so
the SQLite handle is still open when the directory is removed. POSIX
unlinks an open file happily; Windows refuses, and the removal — in a
finally or an afterEach, after the assertions have passed — throws EPERM
and fails the test. 14 tests across the three files, every one of them
green on the assertions themselves.

Closed in the teardown rather than at the end of the test body, so a
failing assertion cannot skip it and turn one red into two.
resolution.test.ts also opens a second connection of its own for a raw
edge query; that one is hoisted out of the try and closed the same way.
A signalled process still holds its file handles until it is actually
gone, and these suites SIGKILL the spawned MCP server and then remove the
directory synchronously — on Windows that is EPERM, and the test fails
after its assertions passed. Await the exit instead of only sending the
signal; mcp-daemon had a fixed 50ms grace period for this, which is now
the same await plus the file's own waitProcessExit for the detached
daemon it reaps by pid.

Awaiting the tracked children is not sufficient on its own: the server
may leave a detached daemon this suite never sees, and mcp-daemon's
concurrent-launcher test deliberately leaves one alive. So the removals
also retry, which is what fs.rmSync's maxRetries is for — 1s of retries
in the three proxy suites, 6s in mcp-daemon where the daemon is the point
of the test.

Full suite on Windows: 4,017 passing, 0 failing, twice in a row; before
this and the previous commit, 24 failing.
@bompus

bompus commented Sep 6, 2026

Copy link
Copy Markdown
Author

Duplicate of #1717, which I opened this morning and did not check before filing this. #1717 is the better patch and supersedes this one: same close-in-teardown for arkts-resolution / frameworks-integration / resolution, same await-the-exit for the mcp-* suites, plus a shared __tests__/rm-temp.ts retry helper instead of retry options inlined at each call site, plus two this PR does not touch — cli-ui-command's waitForMarker existence race and watcher.test.ts. It is also verified over 10 consecutive full runs against my 2 here.

The one thing worth carrying across: I reached the same 'awaiting the tracked child is necessary but not sufficient' conclusion independently here, from a run that still failed 2 of 4,240 with a different pair each time, which is the same detached-daemon case #1717 documents.

Closing this and #1722. Review effort belongs on #1717.

@bompus bompus closed this Sep 6, 2026
@bompus
bompus deleted the fix/windows-temp-dir-handles branch September 6, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tests leak file handles into their temp projects: 24 fail with EPERM on Windows

1 participant