Problem
Every npx vitest run leaves ~7,600 SQLite clone files (~3.8 GB) in the OS temp directory, permanently.
Nothing ever removes them, so the cost is linear in how many times anyone runs the suite.
Measured on a clean, fully-passing run (1369 passed | 3 skipped, 0 failures) immediately after
emptying the temp directory:
|
|
| clone files left behind by one run |
7,577 |
| total size |
~3.8 GB |
| size of one clone |
520,192 bytes |
| distinct worker PIDs that leaked |
400 |
This is not a crash artifact — the run succeeded completely.
Cause
test/helpers/d1.ts:78-108. Every new TestD1Database() copies the migrated template to
${tmpdir()}/loopover-test-clone-<pid>-<n>-<rand>.sqlite3, and the only cleanup is a single
process.on("exit") sweep registered once per worker:
state.clonePaths.push(clonePath);
if (!state.exitSweepRegistered) {
state.exitSweepRegistered = true;
process.on("exit", () => {
for (const path of state.clonePaths) {
try { unlinkSync(path); } catch { /* best-effort tmp hygiene only */ }
}
});
}
With vitest's worker pool, that handler evidently does not run for the great majority of workers — 400
distinct PIDs left files behind in a single successful run. process.on("exit") is the wrong hook for a
pooled worker that is torn down rather than exiting normally, and the catch {} means a failure to unlink
is silent by design.
The obvious alternative — unlink immediately after opening — is explicitly ruled out by the file's own
comment at line 27 ("The clone file must NOT be unlinked while its database is open"), so the fix needs to
be tied to the database being closed, not to process exit.
Impact, which is not theoretical
This filled a 460 GB dev machine. /var/folders/…/T had accumulated 241.2 GB across 496,955
loopover-* files, of which 483,844 were created that same day; the volume hit 100% with 2.0 GB free.
The failure mode is badly misleading. Once the disk fills, the suite does not report "no space" — it reports
hundreds of unrelated test failures. One run showed 1235 failed test files | 730 failed tests, another
518 failed | 267 failed, with assertion errors scattered across api.test.ts,
check-migrations-script.test.ts, selfhost-update-script.test.ts and others. The real cause appeared only
as a couple of buried lines:
Error: ENOSPC: no space left on device, mkdtemp '/var/folders/.../loopover-miner-init-wizard-XXXXXX'
Error: database or disk is full
Deleting only ${tmpdir()}/loopover-* recovered 244 GB and the same suite went green with 26,762
passed / 0 failed and no code change. So this costs real debugging time on failures that look like genuine
regressions, and it will do so again for anyone who runs the suite often enough.
Deliverables
How this was found
Not by looking for it: repeated full-suite runs while working #10269 filled the disk, and the resulting mass
failures were initially indistinguishable from a real regression.
Problem
Every
npx vitest runleaves ~7,600 SQLite clone files (~3.8 GB) in the OS temp directory, permanently.Nothing ever removes them, so the cost is linear in how many times anyone runs the suite.
Measured on a clean, fully-passing run (
1369 passed | 3 skipped, 0 failures) immediately afteremptying the temp directory:
This is not a crash artifact — the run succeeded completely.
Cause
test/helpers/d1.ts:78-108. Everynew TestD1Database()copies the migrated template to${tmpdir()}/loopover-test-clone-<pid>-<n>-<rand>.sqlite3, and the only cleanup is a singleprocess.on("exit")sweep registered once per worker:With vitest's worker pool, that handler evidently does not run for the great majority of workers — 400
distinct PIDs left files behind in a single successful run.
process.on("exit")is the wrong hook for apooled worker that is torn down rather than exiting normally, and the
catch {}means a failure to unlinkis silent by design.
The obvious alternative — unlink immediately after opening — is explicitly ruled out by the file's own
comment at line 27 ("The clone file must NOT be unlinked while its database is open"), so the fix needs to
be tied to the database being closed, not to process exit.
Impact, which is not theoretical
This filled a 460 GB dev machine.
/var/folders/…/Thad accumulated 241.2 GB across 496,955loopover-*files, of which 483,844 were created that same day; the volume hit 100% with 2.0 GB free.The failure mode is badly misleading. Once the disk fills, the suite does not report "no space" — it reports
hundreds of unrelated test failures. One run showed
1235 failed test files | 730 failed tests, another518 failed | 267 failed, with assertion errors scattered acrossapi.test.ts,check-migrations-script.test.ts,selfhost-update-script.test.tsand others. The real cause appeared onlyas a couple of buried lines:
Deleting only
${tmpdir()}/loopover-*recovered 244 GB and the same suite went green with 26,762passed / 0 failed and no code change. So this costs real debugging time on failures that look like genuine
regressions, and it will do so again for anyone who runs the suite often enough.
Deliverables
TestD1Databaselifecycle so a pooled worker that never exits cleanly still cleans up.npx vitest runleaves zeroloopover-test-clone-*files behind. Worth assertingmechanically, since the current failure is silent and only shows up as unrelated red weeks later.
catch {}should stay silent. It is defensible for tmp hygiene, but it is alsowhy 496k files accumulated with no signal.
indefinitely.
loopover-miner-init-wizard-*andloopover-cli-config-*temp dirs appear in the same directory andshould be checked for the same pattern.
How this was found
Not by looking for it: repeated full-suite runs while working #10269 filled the disk, and the resulting mass
failures were initially indistinguishable from a real regression.