From 524ebb0ea39185ad4c1e26ddfb1396ecbe2734bd Mon Sep 17 00:00:00 2001 From: Brad DerManouelian Date: Thu, 23 Apr 2026 13:50:59 +0000 Subject: [PATCH] fix(workers): use require.main === module guard so require() doesn't start workers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous guard combined an ESM-style import.meta check with a CJS fallback: if ( (typeof import.meta !== "undefined" && import.meta.url === pathToFileURL(process.argv[1]).href) || typeof import.meta === "undefined" || (import.meta as any).url === undefined ) { startWorker()... } esbuild compiles each worker to CommonJS (platform: node, format: cjs) and polyfills `import.meta` as a plain object whose `.url` is `undefined`. At runtime that makes `import.meta.url === void 0` always true, so the guard always fires — meaning `require("./forecastWorker")` unintentionally invokes `startWorker()` and all its connection logic. Three workers (forecastWorker, repoCacheWorker, testmoImportWorker) call `process.exit(1)` synchronously when Valkey is unreachable, so the CI smoke test added in #237 died mid-loop and releases blocked on the smoke-test step. Fix: replace with the canonical CJS pattern `require.main === module`. esbuild preserves `require`/`module` in CJS output, and the smoke test can now `require()` each worker without triggering startup side effects — matching the intent stated in the original comment. Drops the `pathToFileURL` import from each worker (was only used by the old guard). --- testplanit/workers/auditLogWorker.ts | 10 ++-------- testplanit/workers/autoTagWorker.ts | 10 ++-------- testplanit/workers/budgetAlertWorker.ts | 10 ++-------- testplanit/workers/copyMoveWorker.ts | 10 ++-------- testplanit/workers/duplicateScanWorker.ts | 10 ++-------- testplanit/workers/elasticsearchReindexWorker.ts | 10 ++-------- testplanit/workers/emailWorker.ts | 10 ++-------- testplanit/workers/forecastWorker.ts | 12 ++---------- testplanit/workers/magicSelectWorker.ts | 10 ++-------- testplanit/workers/notificationWorker.ts | 10 ++-------- testplanit/workers/repoCacheWorker.ts | 8 +------- testplanit/workers/stepSequenceScanWorker.ts | 10 ++-------- testplanit/workers/syncWorker.ts | 10 ++-------- testplanit/workers/testmoImportWorker.ts | 8 +------- 14 files changed, 26 insertions(+), 112 deletions(-) diff --git a/testplanit/workers/auditLogWorker.ts b/testplanit/workers/auditLogWorker.ts index e59f46c3..1ef0766e 100644 --- a/testplanit/workers/auditLogWorker.ts +++ b/testplanit/workers/auditLogWorker.ts @@ -1,6 +1,5 @@ import type { Prisma } from "@prisma/client"; import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { disconnectAllTenantClients, getPrismaClientForJob, @@ -166,13 +165,8 @@ const startWorker = async () => { }); }; -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as unknown as { url?: string })?.url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("[AuditLogWorker] Running as standalone process..."); startWorker().catch((err) => { console.error("[AuditLogWorker] Failed to start:", err); diff --git a/testplanit/workers/autoTagWorker.ts b/testplanit/workers/autoTagWorker.ts index 2bcffc81..d904bf6d 100644 --- a/testplanit/workers/autoTagWorker.ts +++ b/testplanit/workers/autoTagWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { TagAnalysisService } from "../lib/llm/services/auto-tag/tag-analysis.service"; import type { EntityType } from "../lib/llm/services/auto-tag/types"; import { LlmManager } from "../lib/llm/services/llm-manager.service"; @@ -380,13 +379,8 @@ const startWorker = async () => { }); }; -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Auto-tag worker running..."); startWorker().catch((err) => { console.error("Failed to start auto-tag worker:", err); diff --git a/testplanit/workers/budgetAlertWorker.ts b/testplanit/workers/budgetAlertWorker.ts index 6ee50a48..3b63f91c 100644 --- a/testplanit/workers/budgetAlertWorker.ts +++ b/testplanit/workers/budgetAlertWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { disconnectAllTenantClients, getPrismaClientForJob, @@ -99,13 +98,8 @@ const startWorker = async () => { }); }; -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as unknown as { url?: string })?.url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("[BudgetAlertWorker] Running as standalone process..."); startWorker().catch((err) => { console.error("[BudgetAlertWorker] Failed to start:", err); diff --git a/testplanit/workers/copyMoveWorker.ts b/testplanit/workers/copyMoveWorker.ts index ccee92dd..2f4b7e26 100644 --- a/testplanit/workers/copyMoveWorker.ts +++ b/testplanit/workers/copyMoveWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { runWithAuditContext } from "../lib/auditContext"; import type { ActorContextJobData } from "../lib/auditContextEnqueue"; import { @@ -926,13 +925,8 @@ const startWorker = async () => { }); }; -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Copy-move worker running..."); startWorker().catch((err) => { console.error("Failed to start copy-move worker:", err); diff --git a/testplanit/workers/duplicateScanWorker.ts b/testplanit/workers/duplicateScanWorker.ts index 15cdc25f..cb308a62 100644 --- a/testplanit/workers/duplicateScanWorker.ts +++ b/testplanit/workers/duplicateScanWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { DuplicateScanService } from "../lib/services/duplicateScanService"; import { disconnectAllTenantClients, @@ -338,13 +337,8 @@ export function startDuplicateScanWorker() { return worker; } -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Duplicate scan worker running..."); startDuplicateScanWorker(); } diff --git a/testplanit/workers/elasticsearchReindexWorker.ts b/testplanit/workers/elasticsearchReindexWorker.ts index 36d5591e..898f2c7b 100644 --- a/testplanit/workers/elasticsearchReindexWorker.ts +++ b/testplanit/workers/elasticsearchReindexWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { getElasticsearchClient } from "~/services/elasticsearchService"; import { syncProjectIssuesToElasticsearch } from "~/services/issueSearch"; import { syncProjectMilestonesToElasticsearch } from "~/services/milestoneSearch"; @@ -435,13 +434,8 @@ const startWorker = async () => { process.on("SIGTERM", shutdown); }; -// Run the worker if this file is executed directly (works with both ESM and CommonJS) -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Elasticsearch reindex worker running..."); startWorker().catch((err) => { console.error("Failed to start Elasticsearch reindex worker:", err); diff --git a/testplanit/workers/emailWorker.ts b/testplanit/workers/emailWorker.ts index b67e0d97..97da1992 100644 --- a/testplanit/workers/emailWorker.ts +++ b/testplanit/workers/emailWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { sendDigestEmail, sendNotificationEmail, @@ -539,13 +538,8 @@ const startWorker = async () => { process.on("SIGTERM", shutdown); }; -// Run the worker if this file is executed directly (works with both ESM and CommonJS) -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Email worker running..."); startWorker().catch((err) => { console.error("Failed to start email worker:", err); diff --git a/testplanit/workers/forecastWorker.ts b/testplanit/workers/forecastWorker.ts index c34b9116..1e48c039 100644 --- a/testplanit/workers/forecastWorker.ts +++ b/testplanit/workers/forecastWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { runWithAuditContext } from "../lib/auditContext"; import type { ActorContextJobData } from "../lib/auditContextEnqueue"; import { @@ -507,15 +506,8 @@ async function startWorker() { } } -// Conditionally call startWorker only when this file is executed directly -// This check ensures importing the file doesn't automatically start the worker -// Works with both ESM and CommonJS -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { startWorker().catch((err) => { console.error("Failed to start worker:", err); process.exit(1); diff --git a/testplanit/workers/magicSelectWorker.ts b/testplanit/workers/magicSelectWorker.ts index 1c4acc6c..635c7c99 100644 --- a/testplanit/workers/magicSelectWorker.ts +++ b/testplanit/workers/magicSelectWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { createBatches, executeBatches, @@ -1137,13 +1136,8 @@ export function startMagicSelectWorker() { return worker; } -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Magic select worker running..."); startMagicSelectWorker(); } diff --git a/testplanit/workers/notificationWorker.ts b/testplanit/workers/notificationWorker.ts index 0fdf47af..2ee15a56 100644 --- a/testplanit/workers/notificationWorker.ts +++ b/testplanit/workers/notificationWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { disconnectAllTenantClients, getPrismaClientForJob, @@ -262,13 +261,8 @@ const startWorker = async () => { process.on("SIGTERM", shutdown); }; -// Run the worker if this file is executed directly (works with both ESM and CommonJS) -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Notification worker running..."); startWorker().catch((err) => { console.error("Failed to start notification worker:", err); diff --git a/testplanit/workers/repoCacheWorker.ts b/testplanit/workers/repoCacheWorker.ts index 36575a07..32ec8701 100644 --- a/testplanit/workers/repoCacheWorker.ts +++ b/testplanit/workers/repoCacheWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { repoFileCache } from "../lib/integrations/cache/RepoFileCache"; import { disconnectAllTenantClients, @@ -172,12 +171,7 @@ async function startWorker() { } } -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +if (require.main === module) { startWorker().catch((err) => { console.error("Failed to start worker:", err); process.exit(1); diff --git a/testplanit/workers/stepSequenceScanWorker.ts b/testplanit/workers/stepSequenceScanWorker.ts index 36983b31..065e3368 100644 --- a/testplanit/workers/stepSequenceScanWorker.ts +++ b/testplanit/workers/stepSequenceScanWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { disconnectAllTenantClients, getPrismaClientForJob, @@ -237,13 +236,8 @@ export function startStepSequenceScanWorker() { return worker; } -// Run the worker if this file is executed directly -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Step-scan worker running..."); startStepSequenceScanWorker(); } diff --git a/testplanit/workers/syncWorker.ts b/testplanit/workers/syncWorker.ts index 9748a00f..ebe6f531 100644 --- a/testplanit/workers/syncWorker.ts +++ b/testplanit/workers/syncWorker.ts @@ -1,5 +1,4 @@ import { Job, Worker } from "bullmq"; -import { pathToFileURL } from "node:url"; import { runWithAuditContext } from "../lib/auditContext"; import type { ActorContextJobData } from "../lib/auditContextEnqueue"; import { @@ -261,13 +260,8 @@ const startWorker = async () => { process.on("SIGTERM", shutdown); }; -// Run the worker if this file is executed directly (works with both ESM and CommonJS) -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +// Run the worker only when this file is executed directly (not on require) +if (require.main === module) { console.log("Sync worker running..."); startWorker().catch((err) => { console.error("Failed to start sync worker:", err); diff --git a/testplanit/workers/testmoImportWorker.ts b/testplanit/workers/testmoImportWorker.ts index e9595e79..db68453e 100644 --- a/testplanit/workers/testmoImportWorker.ts +++ b/testplanit/workers/testmoImportWorker.ts @@ -15,7 +15,6 @@ import bcrypt from "bcrypt"; import { Job, Worker } from "bullmq"; import { Window as HappyDOMWindow } from "happy-dom"; import { Readable } from "node:stream"; -import { pathToFileURL } from "node:url"; import { emptyEditorContent } from "../app/constants/backend"; import { disconnectAllTenantClients, @@ -7986,12 +7985,7 @@ async function startWorker() { } // Start worker when file is run directly (works with both ESM and CommonJS) -if ( - (typeof import.meta !== "undefined" && - import.meta.url === pathToFileURL(process.argv[1]).href) || - typeof import.meta === "undefined" || - (import.meta as any).url === undefined -) { +if (require.main === module) { startWorker().catch((err) => { console.error("Failed to start Testmo import worker:", err); process.exit(1);