diff --git a/packages/coding-agent/test/bash-executor.test.ts b/packages/coding-agent/test/bash-executor.test.ts index 97f944dc8..7d02a23ee 100644 --- a/packages/coding-agent/test/bash-executor.test.ts +++ b/packages/coding-agent/test/bash-executor.test.ts @@ -8,6 +8,7 @@ import { DEFAULT_MAX_BYTES } from "@oh-my-pi/pi-coding-agent/session/streaming-o import * as shellSnapshot from "@oh-my-pi/pi-coding-agent/utils/shell-snapshot"; import type { Shell } from "@oh-my-pi/pi-natives"; import * as piNatives from "@oh-my-pi/pi-natives"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // Matches the schema default for `tools.artifactHeadBytes` (20 KB) used by // OutputSink when bash-executor pulls settings via resolveOutputSinkHeadBytes. @@ -72,7 +73,7 @@ describe("executeBash", () => { resetSettingsForTest(); vi.restoreAllMocks(); if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); @@ -201,7 +202,7 @@ exit 64 expect(result.output.trim()).toBe("shell-ok"); expect(fs.readFileSync(marker, "utf8")).toContain("-l -c"); } finally { - fs.rmSync(shellDir, { recursive: true, force: true }); + removeSyncWithRetries(shellDir); } }); @@ -262,7 +263,7 @@ exit 64 } else { Bun.env.SHELL = originalShell; } - fs.rmSync(shellDir, { recursive: true, force: true }); + removeSyncWithRetries(shellDir); } }); @@ -304,7 +305,7 @@ exit 64 expect(result.exitCode).toBe(0); expect(result.output.trim()).toBe("zsh-alias-ok"); } finally { - fs.rmSync(shellDir, { recursive: true, force: true }); + removeSyncWithRetries(shellDir); } }); diff --git a/packages/coding-agent/test/compaction-hooks.test.ts b/packages/coding-agent/test/compaction-hooks.test.ts index e591a5005..d9cbe10fb 100644 --- a/packages/coding-agent/test/compaction-hooks.test.ts +++ b/packages/coding-agent/test/compaction-hooks.test.ts @@ -22,7 +22,7 @@ import { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session"; import { AuthStorage } from "@oh-my-pi/pi-coding-agent/session/auth-storage"; import { SessionManager } from "@oh-my-pi/pi-coding-agent/session/session-manager"; import { createTools, type ToolSession } from "@oh-my-pi/pi-coding-agent/tools"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { e2eApiKey } from "./utilities"; describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Compaction hooks", () => { @@ -42,7 +42,7 @@ describe.skipIf(!e2eApiKey("ANTHROPIC_API_KEY"))("Compaction hooks", () => { await session.dispose(); } if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); diff --git a/packages/coding-agent/test/compaction-thinking-model.test.ts b/packages/coding-agent/test/compaction-thinking-model.test.ts index 0a63b3001..414eaf075 100644 --- a/packages/coding-agent/test/compaction-thinking-model.test.ts +++ b/packages/coding-agent/test/compaction-thinking-model.test.ts @@ -12,7 +12,7 @@ import { afterEach, beforeEach, describe } from "bun:test"; import * as fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; -import { Snowflake } from "@oh-my-pi/pi-utils"; +import { removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; import { e2eApiKey } from "./utilities"; // Check for auth @@ -36,7 +36,7 @@ describe.skipIf(!HAS_ANTIGRAVITY_AUTH)("Compaction with thinking models (Antigra authStorage?.close(); authStorage = undefined; if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); }); @@ -61,7 +61,7 @@ describe.skipIf(!HAS_ANTHROPIC_AUTH)("Compaction with thinking models (Anthropic authStorage?.close(); authStorage = undefined; if (tempDir && fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); + removeSyncWithRetries(tempDir); } }); }); diff --git a/packages/coding-agent/test/core/apply-patch.test.ts b/packages/coding-agent/test/core/apply-patch.test.ts index 6b000874b..a2b6f4313 100644 --- a/packages/coding-agent/test/core/apply-patch.test.ts +++ b/packages/coding-agent/test/core/apply-patch.test.ts @@ -11,6 +11,7 @@ import { parseDiffHunks, seekSequence, } from "@oh-my-pi/pi-coding-agent/edit"; +import { removeSyncWithRetries } from "@oh-my-pi/pi-utils"; // ═══════════════════════════════════════════════════════════════════════════ // Test-local adapters over the production Codex envelope API. @@ -215,7 +216,7 @@ describe("apply-patch scenarios", () => { afterEach(() => { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // Ignore cleanup errors } @@ -303,7 +304,7 @@ describe("applyPatch", () => { afterEach(() => { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // Ignore cleanup errors } @@ -458,7 +459,7 @@ describe("simple replace mode", () => { afterEach(() => { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // Ignore cleanup errors } @@ -634,7 +635,7 @@ describe("applyCodexPatch (production)", () => { afterEach(() => { try { - fs.rmSync(tempDir, { recursive: true, force: true }); + removeSyncWithRetries(tempDir); } catch { // ignore } diff --git a/packages/coding-agent/test/tools.test.ts b/packages/coding-agent/test/tools.test.ts index 116dd81b6..0720f31c9 100644 --- a/packages/coding-agent/test/tools.test.ts +++ b/packages/coding-agent/test/tools.test.ts @@ -19,7 +19,7 @@ import { DEFAULT_FILE_LIMIT, MULTI_FILE_PER_FILE_MATCHES, SearchTool } from "@oh import * as toolTimeouts from "@oh-my-pi/pi-coding-agent/tools/tool-timeouts"; import { WriteTool } from "@oh-my-pi/pi-coding-agent/tools/write"; import { unzip } from "@oh-my-pi/pi-coding-agent/utils/zip"; -import { $which, Snowflake } from "@oh-my-pi/pi-utils"; +import { $which, removeSyncWithRetries, Snowflake } from "@oh-my-pi/pi-utils"; // Helper to extract text from content blocks function getTextOutput(result: any): string { @@ -302,7 +302,7 @@ describe("Coding Agent Tools", () => { vi.restoreAllMocks(); // Clean up test directory - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); // Restore original edit variant if (originalEditVariant === undefined) { @@ -2134,7 +2134,7 @@ describe("edit tool CRLF handling", () => { }); afterEach(() => { - fs.rmSync(testDir, { recursive: true, force: true }); + removeSyncWithRetries(testDir); // Restore original edit variant if (originalEditVariant === undefined) { diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index aca7129c0..0cda11748 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- Exported `removeSyncWithRetries()` as a standalone function so tests that manage their own temp dirs can use the same retry-on-EBUSY cleanup logic as `TempDir.removeSync()`. + ## [16.1.3] - 2026-06-19 ### Changed diff --git a/packages/utils/src/temp.ts b/packages/utils/src/temp.ts index 6a8aa557a..1a061854c 100644 --- a/packages/utils/src/temp.ts +++ b/packages/utils/src/temp.ts @@ -95,7 +95,7 @@ async function removeWithRetries(target: string): Promise { } } -function removeSyncWithRetries(target: string): void { +export function removeSyncWithRetries(target: string): void { for (let attempt = 0; ; attempt++) { try { fs.rmSync(target, kRemoveOptions);