Skip to content

test: fix EBUSY database lock failures on Windows during teardown#104

Merged
alfonso-magic-context merged 1 commit into
cortexkit:masterfrom
Zireael:fix/windows-test-db-locks
Jun 3, 2026
Merged

test: fix EBUSY database lock failures on Windows during teardown#104
alfonso-magic-context merged 1 commit into
cortexkit:masterfrom
Zireael:fix/windows-test-db-locks

Conversation

@Zireael
Copy link
Copy Markdown
Contributor

@Zireael Zireael commented May 25, 2026

Fixes #102

Bug Description
The test suite intermittently fails on Windows environments with EBUSY: resource busy or locked errors during teardown. This occurs because SQLite file handles can take a moment to be completely released by the operating system after database connections are closed. Due to strict file locking on Windows, attempts to synchronously delete temporary directories containing these SQLite databases using rmSync(dir, { recursive: true, force: true }) fail.

Fix Details
I wrapped all rmSync calls found in afterEach teardown hooks across the project's test suites within try/catch blocks that silently ignore the error on Windows. Additionally, I modified the
rmSync options to include maxRetries: 10 and retryDelay: 100, providing a small grace period for any lingering SQLite file handles to release gracefully before aborting.


Summary by cubic

Hardened test teardown on Windows to prevent intermittent EBUSY errors from lingering SQLite locks. Cleanup now tolerates delayed handle release and removes temp dirs more reliably across the suite.

  • Bug Fixes
    • Wrapped all rmSync teardowns in try/catch with { maxRetries: 10, retryDelay: 100 }, including historian debug dump cleanup.
    • Closed SQLite handles explicitly in race/lease tests using closeQuietly before cleanup.
    • Fixed plugin root path detection in compartment-lease.test.ts so the script runs from any cwd.
    • Added Bun types reference in migrations-v11.test.ts for the Bun test runner.

Written for commit 8d060c2. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR hardens test teardown across 56 test files to prevent intermittent EBUSY: resource busy or locked failures on Windows, where SQLite file handles may not be fully released before rmSync attempts to delete the temp directory.

  • Wraps every rmSync in afterEach/finally teardown blocks with { maxRetries: 10, retryDelay: 100 } and an outer try/catch to tolerate residual Windows file locks.
  • Adds explicit closeQuietly calls before cleanup in boundary-execution-cas-race.test.ts and lease-related tests that open multiple DB handles, and fixes the pluginRoot path computation in compartment-lease.test.ts to work cross-platform.
  • Adds a /// <reference types="bun-types" /> directive to migrations-v11.test.ts for the Bun test runner type resolution.

Confidence Score: 3/5

Safe to merge for most test files, but sticky-injection-cas-race.test.ts still leaves database handles open before cleanup, so Windows EBUSY failures are not fully resolved there.

The majority of the 56 changed files receive the correct treatment — closing explicit DB handles and wrapping rmSync with retries. However, sticky-injection-cas-race.test.ts opens two WAL handles (a and b) in tests 1 and 2 but never calls closeQuietly on them before rmSync. Those open handles are the direct source of EBUSY on Windows, and without explicit closure the retry loop may still exhaust its attempts and silently leak the temp directory. The boundary-execution-cas-race.test.ts file in the same PR shows the intended fix pattern, making the omission in sticky-injection an inconsistency that will leave intermittent failures on Windows for those specific tests.

packages/plugin/src/features/magic-context/sticky-injection-cas-race.test.ts needs closeQuietly calls for handles a and b before rmSync in tests 1 and 2 (and db in tests 3 and 4).

Important Files Changed

Filename Overview
packages/plugin/src/features/magic-context/sticky-injection-cas-race.test.ts Added try/catch + maxRetries to all four rmSync teardowns, but missing closeQuietly for database handles a and b in tests 1 and 2 — open handles are the root cause of EBUSY, so cleanup may still fail on Windows.
packages/plugin/src/features/magic-context/boundary-execution-cas-race.test.ts Correctly moves DB handle creation before try block, adds closeQuietly(a/b) in finally, and wraps rmSync with maxRetries/retryDelay inside try/catch — the right pattern for Windows EBUSY avoidance.
packages/plugin/src/features/magic-context/compartment-lease.test.ts Adds closeQuietly and retry-guarded rmSync in teardown; replaces endsWith('/packages/plugin') with includes('packages') path detection to fix Windows path separator — the includes check is a substring match that could resolve incorrectly for unusual checkout paths.
packages/plugin/src/features/magic-context/migrations-v11.test.ts Adds bun-types reference directive and wraps afterEach rmSync with maxRetries/retryDelay inside try/catch — straightforward and correct.
packages/plugin/src/hooks/magic-context/compartment-runner.test.ts Wraps both rmSync calls (temp dirs and historian debug dump) with maxRetries/retryDelay inside try/catch — correct pattern.
packages/plugin/src/config/index.test.ts Splits the two sequential rmSync calls into separate try/catch blocks so a failure on xdg cleanup doesn't prevent projectDir cleanup — correct defensive improvement.
packages/plugin/src/shared/conflict-detector.test.ts Same pattern as config/index.test.ts — independent try/catch blocks for each rmSync call with maxRetries/retryDelay.

Comments Outside Diff (1)

  1. packages/plugin/src/features/magic-context/sticky-injection-cas-race.test.ts, line 26-44 (link)

    P1 Missing closeQuietly before rmSync — EBUSY still likely on Windows

    The two database handles a and b opened inside the try block are never explicitly closed before rmSync attempts to delete the directory. On Windows, SQLite WAL handles hold kernel file locks until explicitly released, so these open handles are the direct cause of EBUSY — the same root cause that closeQuietly was added to fix in the parallel boundary-execution-cas-race.test.ts. The try/catch on rmSync silences the error but leaves the temp directory behind every run; with 10 retries at 100 ms each, the GC might collect the handles in time, but this is not guaranteed and differs from the explicit-close pattern used elsewhere in this PR.

    Tests 1 and 2 ("two WAL handles append…" and "append plus prune…") both open a and b via createRaceDb and leave them open. Tests 3 and 4 use a single db handle that is similarly unclosed.

Reviews (1): Last reviewed commit: "test: fix EBUSY database lock failures o..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 57 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/plugin/src/features/magic-context/boundary-execution-cas-race.test.ts">

<violation number="1" location="packages/plugin/src/features/magic-context/boundary-execution-cas-race.test.ts:55">
P2: Move `createRaceDb` calls back inside `try` to preserve teardown guarantee. If `createRaceDb` throws on `b`, `a` leaks and the temp dir is never deleted.</violation>
</file>

Partial review: This PR has more than 50 files, so cubic reviewed the highest-priority files first. During the trial, paid plans get a higher file limit.
You can try an ultrareview to bypass the file limit, comment @cubic-dev-ai ultrareview. Learn more.

Fix all with cubic | Re-trigger cubic

it("15. one WAL handle wins set-if-absent and the other no-ops", () => {
const dir = mkdtempSync(join(tmpdir(), "boundary-exec-race-"));
const path = join(dir, "context.db");
const a = createRaceDb(path);
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Move createRaceDb calls back inside try to preserve teardown guarantee. If createRaceDb throws on b, a leaks and the temp dir is never deleted.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/plugin/src/features/magic-context/boundary-execution-cas-race.test.ts, line 55:

<comment>Move `createRaceDb` calls back inside `try` to preserve teardown guarantee. If `createRaceDb` throws on `b`, `a` leaks and the temp dir is never deleted.</comment>

<file context>
@@ -50,16 +51,22 @@ function payload(id: string): DeferredExecutePayload {
     it("15. one WAL handle wins set-if-absent and the other no-ops", () => {
         const dir = mkdtempSync(join(tmpdir(), "boundary-exec-race-"));
+        const path = join(dir, "context.db");
+        const a = createRaceDb(path);
+        const b = createRaceDb(path);
         try {
</file context>
Fix with Cubic

The test suite intermittently fails on Windows with 'EBUSY: resource busy or locked' errors when attempting to clean up temporary directories. This happens because SQLite file handles take a moment to release after closing the database connections.

Wrapped all rmSync calls in afterEach teardown hooks across the project in try/catch blocks that ignore the error on Windows, and added maxRetries: 10 and retryDelay: 100 to provide a grace period for handles to release.
@ualtinok ualtinok force-pushed the fix/windows-test-db-locks branch from 093f001 to 8d060c2 Compare June 3, 2026 15:54
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 56 files

Re-trigger cubic

@alfonso-magic-context alfonso-magic-context merged commit 58ab807 into cortexkit:master Jun 3, 2026
11 checks passed
Comment on lines +130 to +132
const projectRoot = process.cwd().includes("packages")
? join(process.cwd(), "..", "..")
: process.cwd();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Fragile substring check for project root detection

process.cwd().includes("packages") is a plain substring match, not a path-aware check. It would misfire if the repository is checked out anywhere whose absolute path contains the word "packages" (e.g. /home/packages/magic-context or /opt/node_packages/projects/…), causing .join(cwd, "../..") to walk two levels above an unrelated ancestor. A more robust approach would be to search upward for package.json or packages/plugin using path.resolve, similar to how monorepo tooling discovers workspace roots.

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.

Test suite intermittently fails on Windows with 'EBUSY: resource busy or locked'

2 participants