From 7593842ccfa01b89a83ce5ae999e689bd65121bd Mon Sep 17 00:00:00 2001 From: Huanqi Cao Date: Wed, 12 Aug 2026 15:11:05 +0800 Subject: [PATCH] fix(workflow): forward the tsconfig pin only in the unbuilt worker --- .../workflow-worker-thread/src/host.ts | 13 ++++++---- .../tests/workflow-worker-thread.spec.ts | 24 ++++++++++--------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/packages/workflow/workflow-worker-thread/src/host.ts b/packages/workflow/workflow-worker-thread/src/host.ts index 9e0c3a514a..fee869753f 100644 --- a/packages/workflow/workflow-worker-thread/src/host.ts +++ b/packages/workflow/workflow-worker-thread/src/host.ts @@ -38,18 +38,21 @@ interface ChildRecord { * The unbuilt shape additionally forwards `TSX_TSCONFIG_PATH` for path * resolution. * @param platform - host platform; overridable so tests exercise both peer arms. + * @param tsconfigPath - the tsconfig pin to forward; only the unbuilt caller + * passes one, so the built worker never observes the host's pin. * @returns the scrubbed worker environment object. */ -export function workerSpawnEnv(platform: NodeJS.Platform = process.platform): NodeJS.ProcessEnv { +export function workerSpawnEnv( + platform: NodeJS.Platform = process.platform, + tsconfigPath: string | undefined = undefined, +): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = {} if (platform === 'win32') { const tmp = tmpdir() env.TMP = tmp env.TEMP = tmp } - if (process.env.TSX_TSCONFIG_PATH !== undefined) { - env.TSX_TSCONFIG_PATH = process.env.TSX_TSCONFIG_PATH - } + if (tsconfigPath !== undefined) env.TSX_TSCONFIG_PATH = tsconfigPath return env } @@ -81,7 +84,7 @@ function resolveWorkerSpawn(init: WorkerInit): { entry: string | URL; options: W entry: new URL(`data:text/javascript,${encodeURIComponent(bootstrap)}`), options: { workerData: init, - env: workerSpawnEnv(), + env: workerSpawnEnv(undefined, process.env.TSX_TSCONFIG_PATH), execArgv: [], }, } diff --git a/packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts b/packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts index 275c92269e..5130071362 100644 --- a/packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts +++ b/packages/workflow/workflow-worker-thread/tests/workflow-worker-thread.spec.ts @@ -570,6 +570,11 @@ describe('dsh-workflow-worker-thread', () => { // inside the worker resolves instead of degrading to a cwd-relative // `undefined\temp` (tsx writes its transform cache there). process.env.WORKFLOW_ENV_CANARY = 'leak me' + // The unbuilt worker forwards TSX_TSCONFIG_PATH (a path pin, not a + // credential); clear it so this test observes the empty ambient case + // regardless of the parent's environment. + const tsconfigPath = process.env.TSX_TSCONFIG_PATH + delete process.env.TSX_TSCONFIG_PATH try { const result = await run(ctx, parent, scripted(` const proc = ${ESCAPE} @@ -579,6 +584,8 @@ describe('dsh-workflow-worker-thread', () => { const expectedKeys = process.platform === 'win32' ? ['TEMP', 'TMP'] : [] expect(result.value).toEqual({ canary: null, keys: expectedKeys }) } finally { + if (tsconfigPath === undefined) delete process.env.TSX_TSCONFIG_PATH + else process.env.TSX_TSCONFIG_PATH = tsconfigPath delete process.env.WORKFLOW_ENV_CANARY } }) @@ -591,17 +598,12 @@ describe('dsh-workflow-worker-thread', () => { it('workerSpawnEnv forwards TSX_TSCONFIG_PATH when the snapshot harness pins it', () => { const tsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url)) - vi.stubEnv('TSX_TSCONFIG_PATH', tsconfig) - try { - expect(workerSpawnEnv('linux')).toEqual({ TSX_TSCONFIG_PATH: tsconfig }) - expect(workerSpawnEnv('win32')).toEqual({ - TMP: tmpdir(), - TEMP: tmpdir(), - TSX_TSCONFIG_PATH: tsconfig, - }) - } finally { - vi.unstubAllEnvs() - } + expect(workerSpawnEnv('linux', tsconfig)).toEqual({ TSX_TSCONFIG_PATH: tsconfig }) + expect(workerSpawnEnv('win32', tsconfig)).toEqual({ + TMP: tmpdir(), + TEMP: tmpdir(), + TSX_TSCONFIG_PATH: tsconfig, + }) }) it('the unbuilt worker forwards exactly TSX_TSCONFIG_PATH through the scrub: the paths-map pin survives, secrets do not', async () => {