Skip to content

Cf Workers Per Request Io

Cloudflare Workers — Never Cache Per-Request I/O Across Requests

Section titled “Cloudflare Workers — Never Cache Per-Request I/O Across Requests”

CF Workers forbid using an I/O object — a DB/Hyperdrive socket, a stream, a request/response body, a fetch connection — created in one request handler from a DIFFERENT request handler. Reusing one throws: Cannot perform I/O on behalf of a different request ... (I/O type: ...).

So do NOT cache anything that holds a per-request connection in module scope. The DB client from createDB(env) opens a per-request socket (Hyperdrive/Neon); anything wrapping it (a Better Auth instance via the drizzle adapter, a service bound to db) inherits that constraint.

  • Build per request, every request: createDB(env) and anything binding it — createAuth(createDB(env), …), drizzle adapters, service instances holding db. Construct them INSIDE the request handler and return fresh. Never memoize in a module-level let/singleton.
  • Module-level singletons are ONLY safe for stateless, connection-free objects — e.g. an oRPC OpenAPIHandler built from the static router (db is injected per request via context, so the handler holds no socket). If in doubt, don’t cache.
  • env: read per request from cloudflare:workers (see secret-env-typing / the infisical-secrets skill); don’t stash it module-global either.

A cached instance “works” on the request that created it and on single-request probes. It fails only on a LATER request in the same warm isolate — e.g. the OAuth sign-in request opens the DB connection, the OAuth callback request reuses the cached auth instance and the verifications lookup throws → Better Auth redirects to ?error=please_restart_the_process and the user bounces back to login with no session. Connection-free paths (an empty get-session with no cookie) hide it. Treat any “works once, then breaks on the next request” Worker bug as cross-request I/O reuse until ruled out.