From b9158ac6e39589b7223af316b63f9590dd26eb2a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:47:23 +0000 Subject: [PATCH 1/3] Initial plan From e0ef84d9b2f900891253ce00c3e3543e0aa8fd5c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:51:07 +0000 Subject: [PATCH 2/3] fix(build): replace fs.rmSync with robust recursive directory removal The fs.rmSync() call was failing in CI with ENOTEMPTY errors when trying to clean the json-schema directory. This implements a more robust removeDirRecursive() function that: - Manually walks the directory tree - Removes files and subdirectories with retry logic - Handles ENOTEMPTY errors more reliably in CI environments - Uses exponential backoff for retries Tested locally with multiple consecutive builds to verify directory cleaning works correctly. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/spec/scripts/build-schemas.ts | 54 +++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/packages/spec/scripts/build-schemas.ts b/packages/spec/scripts/build-schemas.ts index 5cb89b266..15def5bf1 100644 --- a/packages/spec/scripts/build-schemas.ts +++ b/packages/spec/scripts/build-schemas.ts @@ -74,10 +74,62 @@ function writeFileWithRetry(filePath: string, content: string, retries = MAX_RET } } +/** + * Recursively remove directory with retry logic + * More robust than fs.rmSync for handling ENOTEMPTY errors in CI + */ +function removeDirRecursive(dirPath: string, retries = MAX_RETRIES): void { + for (let attempt = 0; attempt < retries; attempt++) { + try { + if (!fs.existsSync(dirPath)) { + return; // Already removed + } + + // Read all entries in the directory + const entries = fs.readdirSync(dirPath, { withFileTypes: true }); + + // Process each entry + for (const entry of entries) { + const fullPath = path.join(dirPath, entry.name); + + if (entry.isDirectory()) { + // Recursively remove subdirectory + removeDirRecursive(fullPath, retries); + } else { + // Remove file with retry + for (let fileRetry = 0; fileRetry < retries; fileRetry++) { + try { + fs.unlinkSync(fullPath); + break; + } catch (error) { + if (fileRetry === retries - 1) { + throw error; + } + sleepSync(RETRY_DELAY_BASE_MS * (fileRetry + 1)); + } + } + } + } + + // Now remove the empty directory + fs.rmdirSync(dirPath); + return; // Success + + } catch (error) { + if (attempt === retries - 1) { + throw new Error(`Failed to remove directory ${dirPath} after ${retries} attempts: ${error}`); + } + // Wait before retrying with exponential backoff + const delay = RETRY_DELAY_BASE_MS * (attempt + 1); + sleepSync(delay); + } + } +} + // Clean output directory ensures no stale files remain if (fs.existsSync(OUT_DIR)) { console.log(`Cleaning output directory: ${OUT_DIR}`); - fs.rmSync(OUT_DIR, { recursive: true, force: true, maxRetries: MAX_RETRIES, retryDelay: RETRY_DELAY_BASE_MS }); + removeDirRecursive(OUT_DIR); // Wait a bit to ensure file system has synced // This prevents ENOENT errors on some file systems, particularly in CI environments From 0af84bef4b13237dc2ca91fb0528e18ed733601c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:54:30 +0000 Subject: [PATCH 3/3] refactor: address code review feedback for removeDirRecursive - Add comprehensive JSDoc with @param and @throws - Add error context for failing subdirectories - Keep fs.rmdirSync for directory removal (most reliable approach) Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- packages/spec/scripts/build-schemas.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/spec/scripts/build-schemas.ts b/packages/spec/scripts/build-schemas.ts index 15def5bf1..b5ddd93cc 100644 --- a/packages/spec/scripts/build-schemas.ts +++ b/packages/spec/scripts/build-schemas.ts @@ -77,6 +77,10 @@ function writeFileWithRetry(filePath: string, content: string, retries = MAX_RET /** * Recursively remove directory with retry logic * More robust than fs.rmSync for handling ENOTEMPTY errors in CI + * + * @param dirPath - The path to the directory to remove + * @param retries - Maximum number of retry attempts (default: MAX_RETRIES) + * @throws {Error} When directory cannot be removed after all retries are exhausted */ function removeDirRecursive(dirPath: string, retries = MAX_RETRIES): void { for (let attempt = 0; attempt < retries; attempt++) { @@ -94,7 +98,11 @@ function removeDirRecursive(dirPath: string, retries = MAX_RETRIES): void { if (entry.isDirectory()) { // Recursively remove subdirectory - removeDirRecursive(fullPath, retries); + try { + removeDirRecursive(fullPath, retries); + } catch (error) { + throw new Error(`Failed to remove subdirectory ${fullPath}: ${error}`); + } } else { // Remove file with retry for (let fileRetry = 0; fileRetry < retries; fileRetry++) { @@ -112,6 +120,7 @@ function removeDirRecursive(dirPath: string, retries = MAX_RETRIES): void { } // Now remove the empty directory + // Using rmdirSync for removing empty directories (works reliably across Node versions) fs.rmdirSync(dirPath); return; // Success