fetchArrayTypes() promise is discarded in ReadyForQuery: a failing fetch_types query crashes the process via unhandled rejection
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 8.7k
- Forks
- 374
- Avg merge
- 11d 16h
- Merged PRs (30d)
- 1
Description
The defect
ReadyForQuery in src/connection.js calls the async fetchArrayTypes() and discards its promise:
if (needsTypes) {
initial.reserve && (initial = null)
return fetchArrayTypes()
}
fetchArrayTypes() awaits the internal pg_type catalog query. That query runs as the first statement on every new connection (with the default fetch_types: true). If it fails, the awaited Query rejects, the async function's promise rejects, and nothing ever subscribed to it, so the raw PostgresError surfaces as an unhandledRejection. Runtimes that exit on unhandled rejections kill the process (we observed Node.js process exited with exit status: 128 on Vercel, once per fresh connection during a database incident). The user's own query is unaffected by the discard: it executes afterwards and fails or succeeds on its own terms, so the crash is not catchable from user code.
This is the same signature as #279 (2022), which was closed as not reproducible at the time. It reproduces deterministically now.
Minimal reproduction (postgres 3.4.9, Node 22, plain Postgres 17)
A 1ms statement_timeout bound at the startup handshake reliably cancels the internal catalog query before it completes:
import postgres from 'postgres'
process.on('unhandledRejection', (e) => console.log('UNHANDLED', e.code, e.message))
const sql = postgres(process.env.DATABASE_URL, {
prepare: false,
connection: { statement_timeout: 1 },
})
try {
await sql`select pg_sleep(2)`
} catch (e) {
console.log('caught', e.code)
}
Output:
caught 57014
UNHANDLED 57014 canceling statement due to statement timeout
Any other failure of the types query produces the same shape; statement_timeout is just the deterministic trigger. In production the trigger was a pooler incident where fresh backends cancelled their first statements.
Suggested fix
Observe the promise at the call site. This preserves the current semantics exactly: on a types-fetch failure the connection already continues without array types and already executes the pending initial query, so the only change is that the rejection no longer escapes:
if (needsTypes) {
initial.reserve && (initial = null)
return fetchArrayTypes().catch(() => { /* connection proceeds without array types */ })
}
Verified present in 3.4.9 and on master as of 2026-08-13 (same shape in src, cjs/src, and cf/src). We are running exactly this one-line change as a patch in production. Happy to open a PR if useful.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at ReadyForQuery in src/connection.js and compare the corresponding paths in cjs/src and cf/src; run the minimal Node 22/Postgres reproduction with a 1ms statement timeout. Done means the cancellation remains catchable by the user's query and no unhandledRejection is emitted from the internal types query.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js, postgresql
- Domain
- database
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100