From e5daafde0a4e8e8966ba2f56043425c736af8f69 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 21:54:13 +0800 Subject: [PATCH 1/2] fix(worker): boot ingestion worker through the server-only-aware tsx runner Dockerfile.worker ran `node node_modules/tsx/dist/cli.mjs worker/index.ts` directly. worker/index.ts imports src/lib/env, which begins with `import "server-only"` (added in #488). Under bare tsx that import throws, so the production ingestion worker container crash-loops on boot. Route the entrypoint through scripts/run-tsx.mjs, which registers the server-only stub hook so the import resolves outside the Next bundler. Guard it in tests/tsx-server-only-runner.test.ts: the worker CMD must route through run-tsx.mjs and never invoke bare tsx, so this cannot regress. Co-Authored-By: Claude Fable 5 --- Dockerfile.worker | 8 +++++++- tests/tsx-server-only-runner.test.ts | 12 ++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/Dockerfile.worker b/Dockerfile.worker index 26c1499d9..f35253400 100644 --- a/Dockerfile.worker +++ b/Dockerfile.worker @@ -35,4 +35,10 @@ COPY . . USER node # Long-poll worker; WORKER_* env vars control claim batch size, concurrency, # and the stale-claim window (see src/lib/env.ts). -CMD ["node", "node_modules/tsx/dist/cli.mjs", "worker/index.ts"] +# +# Route through scripts/run-tsx.mjs, NOT bare tsx: worker/index.ts imports +# src/lib/env, which begins with `import "server-only"`. Under bare tsx that +# import throws and the worker crash-loops on boot. run-tsx.mjs registers the +# server-only stub hook so the import resolves outside the Next bundler. +# Guarded by tests/tsx-server-only-runner.test.ts. +CMD ["node", "scripts/run-tsx.mjs", "worker/index.ts"] diff --git a/tests/tsx-server-only-runner.test.ts b/tests/tsx-server-only-runner.test.ts index 78b164f07..d2f4bc382 100644 --- a/tests/tsx-server-only-runner.test.ts +++ b/tests/tsx-server-only-runner.test.ts @@ -16,6 +16,18 @@ describe("standalone TSX server-only compatibility", () => { expect(packageJson.scripts["check:supabase-project"]).toContain("scripts/run-tsx.mjs"); }); + it("boots the worker image through the server-only-aware runner, not bare tsx", () => { + const dockerfile = readFileSync(new URL("../Dockerfile.worker", import.meta.url), "utf8"); + const cmdLine = dockerfile.split(/\r?\n/).find((line) => line.trimStart().startsWith("CMD")); + expect(cmdLine, "Dockerfile.worker must define a CMD").toBeDefined(); + // worker/index.ts imports src/lib/env, which is `import "server-only"`. + // Bare tsx throws on that import at boot; the entrypoint must route through + // scripts/run-tsx.mjs, which registers the server-only stub hook. + expect(cmdLine).toContain("scripts/run-tsx.mjs"); + expect(cmdLine).toContain("worker/index.ts"); + expect(cmdLine).not.toContain("tsx/dist/cli.mjs"); + }); + it("keeps the Next server-only marker while stubbing it only for standalone runners", () => { expect(readFileSync(new URL("../src/lib/env.ts", import.meta.url), "utf8")).toMatch(/^import ["']server-only["'];/); expect(readFileSync(new URL("../scripts/register-server-only.mjs", import.meta.url), "utf8")).toContain( From 964564f0477635d252238612586e1f83dda3b245 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:00:41 +0800 Subject: [PATCH 2/2] test(worker): assert the exact worker CMD exec vector, not substrings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses CodeRabbit review on #493: parse the JSON exec-form CMD and assert the full ["node","scripts/run-tsx.mjs","worker/index.ts"] vector so bare-tsx variants (["tsx", …], ["npx","tsx", …], or reordered args) cannot slip past substring checks. Co-Authored-By: Claude Fable 5 --- tests/tsx-server-only-runner.test.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/tsx-server-only-runner.test.ts b/tests/tsx-server-only-runner.test.ts index d2f4bc382..3f6d058e1 100644 --- a/tests/tsx-server-only-runner.test.ts +++ b/tests/tsx-server-only-runner.test.ts @@ -22,10 +22,13 @@ describe("standalone TSX server-only compatibility", () => { expect(cmdLine, "Dockerfile.worker must define a CMD").toBeDefined(); // worker/index.ts imports src/lib/env, which is `import "server-only"`. // Bare tsx throws on that import at boot; the entrypoint must route through - // scripts/run-tsx.mjs, which registers the server-only stub hook. - expect(cmdLine).toContain("scripts/run-tsx.mjs"); - expect(cmdLine).toContain("worker/index.ts"); - expect(cmdLine).not.toContain("tsx/dist/cli.mjs"); + // scripts/run-tsx.mjs, which registers the server-only stub hook. Assert the + // exact exec-form vector so no bare-tsx variant (["tsx", …], ["npx","tsx", …], + // tsx/dist/cli.mjs, or a reordered command) can slip through. + const bracket = cmdLine!.indexOf("["); + expect(bracket, "worker CMD must use JSON exec form").toBeGreaterThan(-1); + const execVector = JSON.parse(cmdLine!.slice(bracket)) as string[]; + expect(execVector).toEqual(["node", "scripts/run-tsx.mjs", "worker/index.ts"]); }); it("keeps the Next server-only marker while stubbing it only for standalone runners", () => {