diff --git a/apps/desktop/src/main/remote-agent/__tests__/groupRelay.test.ts b/apps/desktop/src/main/remote-agent/__tests__/groupRelay.test.ts index 38f7e65db38..7cfb37fc404 100644 --- a/apps/desktop/src/main/remote-agent/__tests__/groupRelay.test.ts +++ b/apps/desktop/src/main/remote-agent/__tests__/groupRelay.test.ts @@ -9,6 +9,7 @@ import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; +import { setTimeout as delay } from 'node:timers/promises'; import { REMOTE_AGENT_MAX_RUNS_PER_CONTROLLER } from '@cindy/device-link'; import type { AgentEvent, AgentSessionHandle } from '@cindy/maker-core'; @@ -205,8 +206,20 @@ beforeEach(() => { root = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cindy-group-relay-'))); }); -afterEach(() => { - fs.rmSync(root, { recursive: true, force: true }); +afterEach(async () => { + // The hosts persist run directories and guest-session state in fire-and-forget promises + // (mkdir + tmp + rename), so a write can still be landing when a test ends: a one-shot rmdir + // sees ENOTEMPTY, and a removal that wins the race is undone when the write recreates `root`. + // Remove asynchronously so those writes can finish, and only stop once `root` has stayed gone + // across two consecutive quiet windows; a removal that still hits ENOTEMPTY is retried by the + // loop rather than failing the hook. If something keeps recreating it, fail instead of leaking. + let absentChecks = 0; + for (let attempt = 0; attempt < 40 && absentChecks < 2; attempt += 1) { + await fs.promises.rm(root, { recursive: true, force: true, maxRetries: 3, retryDelay: 20 }).catch(() => undefined); + await delay(50); + absentChecks = fs.existsSync(root) ? 0 : absentChecks + 1; + } + if (absentChecks < 2) throw new Error(`test directory ${root} kept being recreated after cleanup`); }); let runSeq = 0;