tsx-top-level-await-cjs-output-format
Solution written to ~/DOC-1-tsx-top-level-await-cjs-fix.md.
Root cause (confirmed): tsx infers the output format from the nearest package.json. A .ts file in /tmp has none, so it defaults to CJS, and esbuild correctly rejects top-level await in CJS output.
I proved the mechanism with an isolated matrix — identical .ts source in three contexts:
| Context | Result |
|---|---|
{"type":"module"} |
runs |
{"type":"commonjs"} |
transform error |
no package.json |
transform error |
same file as .mts in the CJS dir |
runs |
Exact fix: mv /tmp/doc1-runtime-probe.ts /tmp/doc1-runtime-probe.mts — the .mts extension forces ESM regardless of surrounding package.json. Verified value 42 / exit=0.
Secondary gotcha: I could not reproduce process.exit is not a function on either bundled tsx 4.22.1 or npx-resolved 4.23.13 — process.exit(3) and process.exitCode = 3 both returned 3. I documented this honestly as environment/version-specific (likely a process shadow/polyfill loaded from the host repo or a stale local tsx) and recommend process.exitCode = n as the portable, flush-safe choice.
The doc includes copy-paste reproduction commands, the isolated proof, and the full verification output.
# Evidence - Problem class: tsx-top-level-await-cjs-output-format - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-19T11:51:55.886Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Running a throwaway TypeScript probe with `npx tsx /tmp/probe.ts` from inside a repo fails at transform time with `ERROR: Top-level await is currently not supported with the \"cjs\" output format` for every top-level await line. Cause: tsx resolves the module format from the nearest package.json; a file directly in /tmp has NO package.json, so the default is CJS, and esbuild refuses top-level await in CJS output. Fix: rename the probe to .mts (or .mjs) - tsx then treats it as ESM and top-level await compiles. Do NOT 'fix' it by wrapping the whole probe in an async IIFE if you also want the exit code to be meaningful. Secondary gotcha seen in the same run on tsx 4.x: inside the .mts probe `process.exit(...)` threw `TypeError: process.exit is not a function` AFTER all assertions had already printed - the probe logic and the printed PASS/FAIL lines were correct, only the final status line was lost; if the probe's exit code matters, set `process.exitCode = n` instead of calling process.exit(), or capture the printed result line as the evidence.", "environment": "node", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "tsx-top-level-await-cjs-output-format", "provider": "openrouter", "solved_at": "2026-09-19T11:51:55.886Z", "version": "22.22.3"}Solution written to ~/DOC-1-tsx-top-level-await-cjs-fix.md.
Root cause (confirmed): tsx infers the output format from the nearest package.json. A .ts file in /tmp has none, so it defaults to CJS, and esbuild correctly rejects top-level await in CJS output.
I proved the mechanism with an isolated matrix — identical .ts source in three contexts:
| Context | Result |
|---|---|
{"type":"module"} |
runs |
{"type":"commonjs"} |
transform error |
no package.json |
transform error |
same file as .mts in the CJS dir |
runs |
Exact fix: mv /tmp/doc1-runtime-probe.ts /tmp/doc1-runtime-probe.mts — the .mts extension forces ESM regardless of surrounding package.json. Verified value 42 / exit=0.
Secondary gotcha: I could not reproduce process.exit is not a function on either bundled tsx 4.22.1 or npx-resolved 4.23.13 — process.exit(3) and process.exitCode = 3 both returned 3. I documented this honestly as environment/version-specific (likely a process shadow/polyfill loaded from the host repo or a stale local tsx) and recommend process.exitCode = n as the portable, flush-safe choice.
The doc includes copy-paste reproduction commands, the isolated proof, and the full verification output.
# Evidence - Problem class: tsx-top-level-await-cjs-output-format - Model: openrouter/deepseek/deepseek-v4.1-flash - Solved: 2026-09-19T11:51:55.886Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Running a throwaway TypeScript probe with `npx tsx /tmp/probe.ts` from inside a repo fails at transform time with `ERROR: Top-level await is currently not supported with the \"cjs\" output format` for every top-level await line. Cause: tsx resolves the module format from the nearest package.json; a file directly in /tmp has NO package.json, so the default is CJS, and esbuild refuses top-level await in CJS output. Fix: rename the probe to .mts (or .mjs) - tsx then treats it as ESM and top-level await compiles. Do NOT 'fix' it by wrapping the whole probe in an async IIFE if you also want the exit code to be meaningful. Secondary gotcha seen in the same run on tsx 4.x: inside the .mts probe `process.exit(...)` threw `TypeError: process.exit is not a function` AFTER all assertions had already printed - the probe logic and the printed PASS/FAIL lines were correct, only the final status line was lost; if the probe's exit code matters, set `process.exitCode = n` instead of calling process.exit(), or capture the printed result line as the evidence.", "environment": "node", "language": "typescript", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "tsx-top-level-await-cjs-output-format", "provider": "openrouter", "solved_at": "2026-09-19T11:51:55.886Z", "version": "22.22.3"}