#1203·postgres

Cloudflare/workerd: sql.reserve() never resolves on a pool that has not yet opened a connection

Author: AlphaRecoveryCreated Aug 28, 2026Updated Aug 28, 2026

Summary

On the Cloudflare Workers build (cf/), sql.reserve() never resolves if the pool has not yet opened a connection. It does not reject or time out — the returned promise simply stays pending forever.

Once any query has run on the pool, reserve() works normally and keeps working, because releasing returns the connection to open.

This bites anything that reserves as its first act. kysely-postgres-js acquires connections with postgres.reserve(), so the very first Kysely query against a fresh client hangs indefinitely.

Version: [email protected], workerd via @cloudflare/vitest-plugin (also reachable through wrangler dev).

Reproduction

javascript
import postgres from 'postgres'

// Cold pool — hangs forever.
const cold = postgres(connectionString, { max: 1, fetch_types: false })
await cold.reserve()          // never settles

// Same pool, after one query — fine.
const warm = postgres(connectionString, { max: 1, fetch_types: false })
await warm`SELECT 1`
await warm.reserve()          // resolves immediately

Observed with max: 1 and max: 2 alike, so it is not pool-size exhaustion.

Where it seems to come from

reserve() in cf/src/index.js:

javascript
const c = open.length
  ? open.shift()
  : await new Promise((resolve, reject) => {
      const query = { reserve: resolve, reject }
      queries.push(query)
      closed.length && connect(closed.shift(), query)
    })

On a cold pool open is empty, so it queues a waiter whose only chance of being driven is the closed.length && connect(...) on the same line. If nothing is sitting in closed at that moment — or if connect() does not carry the queued reserve waiter through to resolution on this runtime — nothing ever resolves it. The socket polyfill's connect() is async and dynamically imports cloudflare:sockets, so connection setup here does not make synchronous progress the way it does on Node, which may be the relevant difference.

I have not traced it far enough to propose a patch with confidence, so this is a report rather than a PR — happy to dig further if the diagnosis above looks like it is pointing at the right place.

Workaround

Run one trivial query before handing the pool to anything that reserves:

javascript
const sql = postgres(connectionString, { max: 1, fetch_types: false })
await sql`SELECT 1`

Related: #1202 (unhandled rejection on sql.end() in the same build).