From 28c5fe7b289e4a128322462703b616ffd7b07517 Mon Sep 17 00:00:00 2001 From: Mahima Shanware Date: Thu, 9 Apr 2026 20:16:43 +0000 Subject: [PATCH] fix(core): mitigate Windows EPERM flakiness in project registry saves Introduces a retry mechanism for fs.promises.rename when saving the project registry. This resolves a known concurrency issue on Windows CI runners where multiple processes spinning up simultaneously during E2E tests cause file lock contention. --- packages/core/src/config/projectRegistry.ts | 35 ++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/packages/core/src/config/projectRegistry.ts b/packages/core/src/config/projectRegistry.ts index c58fb55ce8..e8f9cc8e55 100644 --- a/packages/core/src/config/projectRegistry.ts +++ b/packages/core/src/config/projectRegistry.ts @@ -83,17 +83,30 @@ export class ProjectRegistry { await fs.promises.mkdir(dir, { recursive: true }); } - try { - const content = JSON.stringify(data, null, 2); - // Use a randomized tmp path to avoid ENOENT crashes when save() is called concurrently - const tmpPath = this.registryPath + '.' + randomUUID() + '.tmp'; - await fs.promises.writeFile(tmpPath, content, 'utf8'); - await fs.promises.rename(tmpPath, this.registryPath); - } catch (error) { - debugLogger.error( - `Failed to save project registry to ${this.registryPath}:`, - error, - ); + let attempt = 0; + const maxAttempts = 5; + const retryDelayMs = 100; + + while (attempt < maxAttempts) { + try { + const content = JSON.stringify(data, null, 2); + // Use a randomized tmp path to avoid ENOENT crashes when save() is called concurrently + const tmpPath = this.registryPath + '.' + randomUUID() + '.tmp'; + await fs.promises.writeFile(tmpPath, content, 'utf8'); + await fs.promises.rename(tmpPath, this.registryPath); + return; // Success + } catch (error) { + attempt++; + if (attempt >= maxAttempts) { + debugLogger.error( + `Failed to save project registry to ${this.registryPath} after ${maxAttempts} attempts:`, + error, + ); + } else { + // Wait before retrying (exponential backoff could be used here too) + await new Promise((resolve) => setTimeout(resolve, retryDelayMs)); + } + } } }