fix(test): prevent memory and resource leaks in test infrastructure
GitHub Issue Draft
Title: `fix(test): prevent memory and resource leaks in test infrastructure
Type: bug
Labels: bug, perf, test, infrastructure
Description
Critical resource leaks in test infrastructure causing disk/memory bloat and OOM crashes during development.
When running tests (bun test or bun test --watch), the test framework accumulates resources without cleanup, leading to:
- Temporary directories never being deleted
- Spawned processes leaking on test failures
- Eventually triggering OOM killer (vitest workers consuming ~900MB each)
- Terminals closing unexpectedly (appears as "freezes" to developers)
This affects anyone running tests locally, especially in watch mode.
Evidence
System Logs (journalctl)
Jan 17 13:14 - vitest worker memory usage:
• vitest 1: 897 MB
• vitest 2: 900 MB
• vitest 3: 910 MB
• vitest 4: 905 MB
• vitest 5: 898 MB
• vitest 7: 912 MB
Total: ~5.3 GB for test workers alone!
OOM Killer Activity
Jan 17 13:14:28 lemarchy kernel: Out of memory: Killed process 1750015 (node (vitest 7))
total-vm:4985788kB, anon-rss:3646756kB, file-rss:620kB
Jan 17 11:52:14 lemarchy kernel: Out of memory: Killed process 1627971 (node (vitest 3))
total-vm:3727456kB, anon-rss:2268820kB
V8 stack trace shows:
V8::FatalProcessOutOfMemory
→ Node aborts (signal 6/ABRT)
→ Terminal closes
System State
- Swap: 3.9 GB / 4 GB used (95% full)
- Workers: 6-11 vitest workers spawned per run
- Impact: System becomes sluggish, eventually unresponsive
Root Causes
1. Temp Directory Cleanup Disabled
File: packages/opencode/test/fixture/fixture.ts
const result = {
[Symbol.asyncDispose]: async () => {
await options?.dispose?.(dirpath)
// await fs.rm(dirpath, { recursive: true, force: true }) // ❌ COMMENTED OUT
},
path: realpath,
extra: extra as T,
}
Every tmpdir() fixture call creates a permanent directory that is never deleted.
2. Process Leaks in LSP Tests
File: packages/opencode/test/lsp/client.test.ts
test("handles workspace/workspaceFolders request", async () => {
const handle = spawnFakeServer() as any
const client = await Instance.provide({...}) // ❌ If this throws, handle leaks
await client.shutdown() // Never reached if above throws
})
Fake LSP server processes are spawned for each test but only cleaned up if test completes successfully.
Impact
- Developers: Test runs consume increasing amounts of memory/disk space
- CI/CD: May experience OOM failures in test pipelines
- System: Swap fills up, causing system-wide slowdowns
- Experience: Terminals "freeze" then close, disrupting development workflow
Steps to Reproduce
Temp Directory Leak
- Run tests multiple times:
cd packages/opencode bun test - Check
/tmp/for accumulated directories:ls -la /tmp/ | grep opencode-test - Observe hundreds of persistent temp directories
Process Leak
- Force a test to fail in
packages/opencode/test/lsp/client.test.ts:test("handles workspace/workspaceFolders request", async () => { const handle = spawnFakeServer() as any throw new Error("Force failure to test cleanup") // ✅ Process leaks here }) - Run tests:
bun test - Check for orphan Node.js processes:
ps aux | grep fake-lsp-server - Observe processes remain after test completes
Expected Behavior
- Temp directories: Should be deleted when test fixtures are disposed
- Processes: Should always be cleaned up, even on test failures
- Memory: Should remain stable across test runs in watch mode
Actual Behavior
-
Temp directories: Accumulate in
/tmp/directory forever - Processes: Leak when tests fail before cleanup
- Memory: Grows until OOM killer terminates tests
Related Issues
-
#7261 - "v1.1.6 still has memory bloat - heap not released + MCP orphan processes"
- Similar pattern of resource leaks (MCP servers not terminated)
- Our fix addresses test infrastructure specifically
-
#8899 / #8898 - "test: tests fail for developers with global git hooks"
- Also touches
test/fixture/fixture.ts - Shows test infrastructure needing isolation improvements
- Also touches
Proposed Fix
See PR: fix(test): prevent memory and resource leaks
Changes
- Re-enable temp directory cleanup in
tmpdir()fixture - Add
try/finallyblocks to LSP client tests ensuring process cleanup - Processes now killed even when tests throw exceptions
Files Changed
-
packages/opencode/test/fixture/fixture.ts- Uncomment cleanup line -
packages/opencode/test/lsp/client.test.ts- Add try/finally blocks
Testing Verification
After applying fix, verify:
# 1. Temp directories are cleaned up
cd packages/opencode
bun test --run
ls -la /tmp/ | grep opencode-test # Should show empty or minimal results
# 2. No orphan processes after test failures
# Force a test to fail temporarily, then:
ps aux | grep fake-lsp-server # Should show no processes
# 3. Memory stable in watch mode
watch -n 5 'ps aux | grep "vitest\|bun test" | awk "{sum += \$6} END {print "Total RSS:", sum/1024, "MB"}'
# Should remain stable (not grow indefinitely)
Workaround (Until PR Merged)
For developers experiencing this issue now:
# Add to shell config (~/.zshrc or ~/.bashrc)
export BUN_MAX_THREADS=2
# Disable coverage for development (edit bunfig.toml)
# coverage = false
# Clean up accumulated temp directories manually
rm -rf /tmp/opencode-test-*
OpenCode Version
v1.1.24 (current dev branch)
Environment
- OS: Arch Linux 6.18.3-arch1-1
- Terminal: Alacritty
- Node: v25.2.1
- RAM: 30 GB (Swap: 4 GB, 95% full during test runs)
Additional Notes
This is a first-time contribution. Happy to make any adjustments to the fix or documentation.
Process leaks in LSP tests are a common pattern - any test spawning subprocesses should use try/finally or afterEach hooks for guaranteed cleanup.