Files
better-auth/.postmortem/cloudflare-create-require-runtime.md

110 lines
5.0 KiB
Markdown

# Postmortem: Cloudflare Workers `createRequire` Runtime Helper
## Issue Reference
* [Issue #9983](https://github.com/better-auth/better-auth/issues/9983)
* [Issue #6690](https://github.com/better-auth/better-auth/issues/6690)
* [Issue #6665](https://github.com/better-auth/better-auth/issues/6665)
* [Issue #6638](https://github.com/better-auth/better-auth/issues/6638)
* [PR #6704](https://github.com/better-auth/better-auth/pull/6704)
* [PR #9657](https://github.com/better-auth/better-auth/pull/9657)
## Summary
`better-auth@1.7.0-beta.4` and `1.7.0-beta.5` reintroduced the Cloudflare
Workers startup crash previously seen in the 1.4.6 release line. The published
`better-auth` package emitted this shared rolldown runtime helper:
```js
import { createRequire } from "node:module";
var __require = /* @__PURE__ */ createRequire(import.meta.url);
```
Cloudflare Workers can leave `import.meta.url` undefined in bundled output, so
the package crashed at module evaluation time before application code ran. The
crash affected unrelated subpaths such as `better-auth/db` because the helper
lived in the shared `_virtual/_rolldown/runtime.mjs` file imported by many
entries, not only by the Node-specific code that needed CommonJS interop.
## Recurrence History
This class of bug has already recurred once:
1. **1.4.6 reports (#6638, #6665, #6690)**: Cloudflare Workers crashed during
startup because bundled output eagerly called `createRequire(import.meta.url)`.
2. **PR #6704**: Added a Cloudflare smoke test that fails if Wrangler output
contains `createRequire`, `node:module`, or selected Node-only modules.
3. **PR #9657**: Added `getHttpTestInstance` to `better-auth/test`. The helper
statically imported `listhen`, which pulled in CommonJS-oriented Node server
dependencies when the `better-auth` multi-entry package was built.
4. **1.7.0-beta.4 and beta.5**: The published package again contained the eager
shared rolldown `__require` helper. The existing smoke test did not fail
because it checked Wrangler's final bundle, not the published package runtime
helper itself.
## Root Cause
### Test-only dependencies polluted the shared package runtime
`packages/better-auth/src/test-utils/index.ts` statically re-exported
`http-test-instance.ts`, and that file statically imported `listhen`.
Because `better-auth/test` is built as an entry in the same `better-auth`
multi-entry build, rolldown included `listhen` and its transitive dependencies
under `dist/node_modules/`. Some of those dependencies need CommonJS interop, so
rolldown added `__require` to the shared runtime helper.
The helper was shared across unrelated entries. A consumer importing
`better-auth/db` did not import `better-auth/test`, but still evaluated the
same runtime helper and crashed in Workers.
### The existing smoke test checked the wrong boundary
The Cloudflare smoke test checked `e2e/smoke/test/fixtures/cloudflare/dist/index.js`
after Wrangler had bundled and tree-shaken the fixture. That is still useful,
but it did not inspect the built `packages/better-auth/dist/_virtual/_rolldown/runtime.mjs`
file that npm publishes.
The package runtime helper is the earlier contract boundary. Once it contains an
eager `createRequire(import.meta.url)`, any downstream bundler that preserves the
helper can crash even if another fixture bundle happens to tree-shake it away.
## Fix
`getHttpTestInstance` now uses `node:http.createServer()` directly instead of
`listhen`. The helper only needs an OS-assigned local HTTP port and a
promise-returning `close()` method, so the external listener dependency was
unnecessary.
The Cloudflare smoke test now checks two boundaries:
1. Wrangler's final Worker bundle still must not contain `createRequire`,
`node:fs`, or `node:module`.
2. The built `better-auth` rolldown runtime helper must not contain
`createRequire`, `node:module`, or `__require`.
## Lesson Learned
1. **Package runtime helpers are part of the edge-runtime contract.** Checking
only the final app bundle can miss a published-package regression.
2. **A test-only export can poison runtime entries in a multi-entry package.**
If a subpath is built in the same package, static imports can alter shared
helpers used by unrelated subpaths.
3. **Do not add external listener/server dependencies to `better-auth/test`
unless the built package runtime is checked afterward.** Prefer Node built-ins
for test helpers when they are sufficient.
4. **Generated `dist` can be stale locally.** Rebuild before declaring this class
fixed or unaffected.
## Prevention
1. Keep the Cloudflare smoke test's direct assertion on
`packages/better-auth/dist/_virtual/_rolldown/runtime.mjs`.
2. When reviewing changes to `packages/better-auth/src/test-utils`, rebuild
`better-auth` and scan the generated runtime helper for `createRequire`,
`node:module`, and `__require`.
3. Reject fixes that only guard `createRequire(import.meta.url)` in generated
output. The safer fix is to avoid introducing CommonJS interop into the
shared runtime helper in the first place.