From 0251404d4924ceceda0fcc12a8527019e6ee46bd Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Mon, 19 Jan 2026 22:05:21 +0100 Subject: [PATCH 1/3] fix(utils): prevent race condition in parallel plugin runs When running the same plugin for multiple projects in parallel (e.g., via Nx targets), the timestamp-based directory naming could collide if two processes started within the same millisecond. This caused one process to delete the output directory while another was still using it, leading to failures. The fix adds process.pid and a random suffix to the directory name, ensuring uniqueness even when multiple processes run simultaneously. --- packages/utils/src/lib/create-runner-files.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/utils/src/lib/create-runner-files.ts b/packages/utils/src/lib/create-runner-files.ts index db0085a5c..31cf20df6 100644 --- a/packages/utils/src/lib/create-runner-files.ts +++ b/packages/utils/src/lib/create-runner-files.ts @@ -13,8 +13,10 @@ export async function createRunnerFiles( pluginSlug: string, configJSON: string, ): Promise { - const timestamp = Date.now().toString(); - const runnerWorkDir = path.join(pluginWorkDir(pluginSlug), timestamp); + // Use timestamp + process ID + random suffix to ensure uniqueness + // This prevents race conditions when running the same plugin for multiple projects in parallel + const uniqueId = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`; + const runnerWorkDir = path.join(pluginWorkDir(pluginSlug), uniqueId); const runnerConfigPath = path.join(runnerWorkDir, 'plugin-config.json'); const runnerOutputPath = path.join(runnerWorkDir, 'runner-output.json'); From 4e24f73a1e94aa3f650fbdedcfd8a2b039892238 Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Mon, 19 Jan 2026 22:17:14 +0100 Subject: [PATCH 2/3] chore: fix lint --- packages/utils/src/lib/create-runner-files.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/utils/src/lib/create-runner-files.ts b/packages/utils/src/lib/create-runner-files.ts index 31cf20df6..6d016ca07 100644 --- a/packages/utils/src/lib/create-runner-files.ts +++ b/packages/utils/src/lib/create-runner-files.ts @@ -15,6 +15,7 @@ export async function createRunnerFiles( ): Promise { // Use timestamp + process ID + random suffix to ensure uniqueness // This prevents race conditions when running the same plugin for multiple projects in parallel + // eslint-disable-next-line @typescript-eslint/no-magic-numbers const uniqueId = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`; const runnerWorkDir = path.join(pluginWorkDir(pluginSlug), uniqueId); const runnerConfigPath = path.join(runnerWorkDir, 'plugin-config.json'); From 49d8341e6d2096e0104d87c72c225299d88451aa Mon Sep 17 00:00:00 2001 From: Nicolas Beaussart Date: Tue, 20 Jan 2026 13:10:23 +0100 Subject: [PATCH 3/3] chore: adress feedbacks --- packages/utils/src/lib/create-runner-files.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/utils/src/lib/create-runner-files.ts b/packages/utils/src/lib/create-runner-files.ts index 6d016ca07..5cb402580 100644 --- a/packages/utils/src/lib/create-runner-files.ts +++ b/packages/utils/src/lib/create-runner-files.ts @@ -1,5 +1,6 @@ import { writeFile } from 'node:fs/promises'; import path from 'node:path'; +import { threadId } from 'node:worker_threads'; import type { RunnerFilesPaths } from '@code-pushup/models'; import { ensureDirectoryExists, pluginWorkDir } from './file-system.js'; @@ -13,10 +14,9 @@ export async function createRunnerFiles( pluginSlug: string, configJSON: string, ): Promise { - // Use timestamp + process ID + random suffix to ensure uniqueness + // Use timestamp + process ID + threadId // This prevents race conditions when running the same plugin for multiple projects in parallel - // eslint-disable-next-line @typescript-eslint/no-magic-numbers - const uniqueId = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 8)}`; + const uniqueId = `${(performance.timeOrigin + performance.now()) * 10}-${process.pid}-${threadId}`; const runnerWorkDir = path.join(pluginWorkDir(pluginSlug), uniqueId); const runnerConfigPath = path.join(runnerWorkDir, 'plugin-config.json'); const runnerOutputPath = path.join(runnerWorkDir, 'runner-output.json');