Skip to content

fix(api): guard initOpenNextCloudflareForDev against duplicate calls#1276

Open
spokodev wants to merge 2 commits into
opennextjs:mainfrom
spokodev:fix/init-dev-clear-error-on-duplicate-call
Open

fix(api): guard initOpenNextCloudflareForDev against duplicate calls#1276
spokodev wants to merge 2 commits into
opennextjs:mainfrom
spokodev:fix/init-dev-clear-error-on-duplicate-call

Conversation

@spokodev

Copy link
Copy Markdown

Closes #1251.

Calling initOpenNextCloudflareForDev more than once in the same Node.js process used to spawn two competing miniflare instances and fail with the obscure workerd SQLITE_BUSY error captured in the issue. The existing shouldContextInitializationRun guard only protects across the two processes that next dev spawns, not against repeated calls in the same one.

Picking Option 1 from the issue (hard fail with a friendly message), per @vicb's comment.

This PR:

  • adds a module-scope flag in packages/cloudflare/src/api/cloudflare-context.ts that flips to true on the first invocation of initOpenNextCloudflareForDev
  • on every subsequent call within the same process it throws an error that names the function, points the user at the Next.js config file, and surfaces the SQLITE_BUSY symptom so a future user with the same workerd output can search and find this guard
  • the existing cross-process guard runs unchanged, after the new check, so the function still no-ops in the secondary next dev process

Verification:

  • new spec at packages/cloudflare/src/api/cloudflare-context.spec.ts covers four cases: first call resolves, second call rejects with the expected message shape, error mentions both the config file and SQLITE_BUSY, and a fresh module import resets the guard so we are not accidentally relying on global state that would survive a real new process
  • vi.stubGlobal("AsyncLocalStorage", undefined) is used to short-circuit shouldContextInitializationRun in the spec so the test isolates the duplicate-call guard from the wrangler / miniflare setup
  • RED baseline: reverting cloudflare-context.ts to main makes 3 of the 4 new tests fail with the expected mismatch, applying the fix turns them green
  • pnpm code:checks clean (prettier + eslint + tsc)
  • full pnpm --filter cloudflare test suite green: 33 files, 332 tests, 0 regressions
  • changeset added at .changeset/init-open-next-cloudflare-for-dev-duplicate-call-guard.md (@opennextjs/cloudflare: patch)

Calling initOpenNextCloudflareForDev twice in the same process used
to spawn two competing miniflare instances and fail with an obscure
workerd SQLITE_BUSY error. Track the invocation at module scope and
throw a user-actionable error that points at the Next.js config file
the moment the second call happens, instead of letting the failure
surface deep inside the Workers runtime.

The existing cross-process guard (shouldContextInitializationRun) is
unchanged - the new check fires before it, so it catches the same-
process case that the cross-process check is not designed to handle.

Closes opennextjs#1251
@changeset-bot

changeset-bot Bot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: caac90a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@opennextjs/cloudflare Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@spokodev spokodev marked this pull request as ready for review June 18, 2026 17:20

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

Open in Devin Review

Comment on lines +256 to +263
export async function initOpenNextCloudflareForDev(options?: GetPlatformProxyOptions) {
if (initOpenNextCloudflareForDevHasBeenCalled) {
throw new Error(
`\n\nERROR: \`initOpenNextCloudflareForDev\` was called more than once in the same process.\n` +
`It should be called exactly once, at the top of your Next.js config file.\n` +
`Multiple calls start competing Workers runtimes, which fail with an obscure workerd \`SQLITE_BUSY\` error.\n`
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 Unhandled rejection when the guard throws in unawaited calls

The function's JSDoc at line 253 says "although async it doesn't need to be awaited". If a user accidentally writes initOpenNextCloudflareForDev() twice without await, the second call returns a rejected promise that is never caught, producing an unhandledRejection event (which crashes Node.js 15+ by default). This is arguably the desired behavior — a loud crash forces the user to notice and fix their config — but it's a change from the previous behavior where a duplicate no-op call would silently succeed. Worth confirming this is the intended DX tradeoff.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Fixed in caac90a: the guard now lives in a synchronous wrapper that delegates the async work to an internal impl, so a duplicate call throws at the call site instead of producing an unhandled rejection when the call isn't awaited. The duplicate-call tests now assert a synchronous throw.

The guard lived inside the async function body, so a second call returned a
rejected promise. Since the function is documented as not needing to be
`await`ed, an unawaited duplicate call surfaced as an unhandledRejection
(which crashes Node 15+) rather than a throw at the call site.

Move the guard into a synchronous wrapper that delegates the async work to an
internal impl, so the duplicate-call error throws at the call site regardless
of awaiting. Tests updated to assert a synchronous throw.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Duplicate calls to initOpenNextCloudflareForDev spawn competing workerds

1 participant