Skip to content

fix(workers): guard generateFromUrlWorker + stub env in smoke test#240

Merged
therealbrad merged 3 commits intomainfrom
fix/workers-main-guard
Apr 24, 2026
Merged

fix(workers): guard generateFromUrlWorker + stub env in smoke test#240
therealbrad merged 3 commits intomainfrom
fix/workers-main-guard

Conversation

@therealbrad
Copy link
Copy Markdown
Contributor

Summary

Follow-up to #239. v0.22.9's smoke-test run surfaced two issues that were hidden behind the broken main-guard:

  1. generateFromUrlWorker.ts had no main guard at all — called startGenerateFromUrlWorker() at module scope unconditionally, so require()ing it tried to construct a BullMQ Worker with no Valkey (Worker requires a connection). Added the same if (require.main === module) wrapper as the other 14 workers.

  2. env.js validates env at module-load time via @t3-oss/env-nextjs, throwing zod invalid_type errors for DATABASE_URL / NEXTAUTH_SECRET / NEXTAUTH_URL. Affected any worker whose transitive imports reach env.js: syncWorker, elasticsearchReindexWorker, copyMoveWorker, duplicateScanWorker, magicSelectWorker.

    Fixed by shimming dummy values at the top of scripts/smoke-test-workers.js using ||=, so real CI-provided env still wins. Rationale: the smoke test is verifying module-graph integrity, not runtime config correctness.

Failure observed in v0.22.9

✗ syncWorker: Invalid environment variables
  (zod: DATABASE_URL, NEXTAUTH_SECRET, NEXTAUTH_URL required)
✗ elasticsearchReindexWorker: Invalid environment variables
✗ copyMoveWorker: Invalid environment variables
✗ duplicateScanWorker: Invalid environment variables
✗ magicSelectWorker: Invalid environment variables
✗ generateFromUrlWorker: Worker requires a connection

Test plan

  • CI smoke test (AMD64 + ARM64) passes after merge on the next release
  • Local build + node scripts/smoke-test-workers.js — all 15 worker entrypoints + scheduler log

🤖 Generated with Claude Code

…start workers

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).
v0.22.9 smoke test surfaced two issues hidden behind the main-guard
bug fixed in the previous commit:

1. generateFromUrlWorker.ts had no main guard at all — it called
   startGenerateFromUrlWorker() unconditionally at module scope, so
   require()'ing it in the smoke test attempted to construct a BullMQ
   Worker with no Valkey and crashed. Wrapped in
   `if (require.main === module)` to match the other 14 workers.

2. env.js validates DATABASE_URL / NEXTAUTH_SECRET / NEXTAUTH_URL at
   module-load time via @t3-oss/env-nextjs, so any worker whose
   transitive imports reach env.js (syncWorker,
   elasticsearchReindexWorker, copyMoveWorker, duplicateScanWorker,
   magicSelectWorker) threw during require with zod 'invalid_type'
   errors. Added dummy env shims at the top of smoke-test-workers.js
   using `||=` so real CI-provided values still win. The smoke test
   is verifying module-graph integrity, not runtime config
   correctness.

Together with the main-guard fix, the smoke test should now complete
cleanly for every worker.
@therealbrad therealbrad merged commit 3e2db54 into main Apr 24, 2026
5 checks passed
@therealbrad therealbrad deleted the fix/workers-main-guard branch April 24, 2026 02:24
@therealbrad
Copy link
Copy Markdown
Contributor Author

🎉 This PR is included in version 0.22.10 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

therealbrad added a commit that referenced this pull request Apr 24, 2026
…t run scheduling (#241)

* fix(workers): use require.main === module guard so require() doesn't start workers

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).

* fix(workers): guard generateFromUrlWorker + stub env in smoke test

v0.22.9 smoke test surfaced two issues hidden behind the main-guard
bug fixed in the previous commit:

1. generateFromUrlWorker.ts had no main guard at all — it called
   startGenerateFromUrlWorker() unconditionally at module scope, so
   require()'ing it in the smoke test attempted to construct a BullMQ
   Worker with no Valkey and crashed. Wrapped in
   `if (require.main === module)` to match the other 14 workers.

2. env.js validates DATABASE_URL / NEXTAUTH_SECRET / NEXTAUTH_URL at
   module-load time via @t3-oss/env-nextjs, so any worker whose
   transitive imports reach env.js (syncWorker,
   elasticsearchReindexWorker, copyMoveWorker, duplicateScanWorker,
   magicSelectWorker) threw during require with zod 'invalid_type'
   errors. Added dummy env shims at the top of smoke-test-workers.js
   using `||=` so real CI-provided values still win. The smoke test
   is verifying module-graph integrity, not runtime config
   correctness.

Together with the main-guard fix, the smoke test should now complete
cleanly for every worker.

* chore(workers): shorten NEXTAUTH_SECRET stub to fit prettier printWidth

* fix(scheduler): add require.main guard so smoke-test require() doesn't run scheduling

scheduler.ts called scheduleJobs() at module top-level, so the v0.22.10
smoke test's require() of dist/scheduler.js tried to connect to Valkey
and exited 1 ('Required queues are not initialized. Cannot schedule
jobs.') even with SKIP_VALKEY_CONNECTION=true (which makes queues no-op
but still marks them unavailable).

Same pattern as the workers fix in #239/#240: gate the runtime code
with `if (require.main === module)`. Production start-workers.sh
invokes scheduler directly via `tsx scheduler.ts`, so require.main IS
module at runtime and scheduleJobs() still runs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant