db-postgres: connect() rejects its `initializing` promise with no argument, producing an unhandledRejection with reason `undefined`
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 76/100
- Tipo de issue
- Error
- Claridad
- Bien especificado
- Estado de actividad
- Activo
- Stack tecnológico
- node.js, postgresql, typescript
Línea de trabajo
Lee packages/db-postgres/src/index.ts y packages/db-postgres/src/connect.ts; después, revisa el tipo de rejectInitializing en packages/drizzle/src/types.ts y el consumidor en packages/drizzle/src/transactions/beginTransaction.ts. El issue describe dos posibles soluciones. Sigue cómo los errores de conexión llegan a la promesa y cómo beginTransaction gestiona el rechazo. La tarea estará terminada cuando el rechazo huérfano ya no informe de undefined y los errores de conexión sigan propagándose.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Describe the Bug
@payloadcms/db-postgres rejects its internal initializing promise with no argument when connect() fails. Because the only thing that ever awaits that promise is beginTransaction, a failed connect on a request path that opens no transaction leaves the rejection unhandled — and since the reason is undefined, Node attaches no stack to it.
The result is an unhandledRejection with reason === undefined. In an app with Sentry (or any unhandledRejection reporter) wired up, that arrives as an event with no message, no stack and no in-app frames, pointing only at the reporter's own handler. It is effectively undebuggable from the report: nothing in it names Payload, Postgres, or the connection at all.
The two lines, at v3.90.2:
-
packages/db-postgres/src/index.ts#L80-L82— the promise is created and no handler is attached:const initializing = new Promise<void>((res, rej) => { resolveInitializing = res rejectInitializing = rej }) -
packages/db-postgres/src/connect.ts#L109-L111— it is rejected with nothing, and then a separate realErroris thrown:if (typeof this.rejectInitializing === 'function') { this.rejectInitializing() // ← no argument ⇒ rejects with `undefined` } throw new Error(`Error: cannot connect to Postgres: ${err.message}`)
Still present on main (packages/db-postgres/src/connect.ts, this.rejectInitializing() immediately before the throw). The type itself guarantees it can never carry a reason — rejectInitializing: () => void, declared with no parameter, in packages/drizzle/src/types.ts and the db-postgres types.
The only consumer is packages/drizzle/src/transactions/beginTransaction.ts (await this.initializing). packages/drizzle/src/destroy.ts replaces the promise rather than awaiting it. So on any read path that does not begin a transaction, nothing is attached when the rejection lands.
Two suggested fixes, either of which is sufficient:
- Pass the error:
this.rejectInitializing(err), and widen the type to(err?: unknown) => void. The rejection then at least carries a stack for whoever is listening. - Attach a no-op handler where the promise is created, so the orphan can never surface:
initializing.catch(() => {}).connect()already throws the real error separately, andbeginTransaction's ownawaitstill rejects, so nothing is swallowed.
(1) is the more useful of the two; doing both costs nothing.
Link to the code that reproduces this issue
https://github.com/payloadcms/payload/blob/v3.90.2/packages/db-postgres/src/connect.ts#L109-L111
I have not pushed a create-payload-app scaffold for this one, because the defect is visible in the published source above and does not depend on app config: rejectInitializing is typed () => void, it is called with no argument, and the promise it rejects has no handler attached at creation. Happy to put a scaffold together if you'd rather have one to run — say so and I will.
Reproduction Steps
Any postgresAdapter app where connect() fails will do it. Forcing it deterministically:
-
Build the app and start it (
output: standalonein my case, but plainnext startbehaves the same). -
Set
connectionTimeoutMillison the adapter'spoollow enough that the pool's timer expires during the first server render, which blocks the event loop while the CMS config is built. WithconnectionTimeoutMillis: 1through10it fires every time:db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL, connectionTimeoutMillis: 1 }, }) -
Request any page that reads from the database but opens no transaction (a plain
payload.find()in a server component is enough). -
Observe two things in the output: Payload's own
Error: cannot connect to Postgres: Connection terminated due to connection timeout— correct and useful — and, separately, anunhandledRejectionwhose reason isundefined.
With a listener installed to inspect it:
process.on('unhandledRejection', (reason) => {
console.log('typeof reason:', typeof reason, '| isError:', reason instanceof Error)
})
// → typeof reason: undefined | isError: false
Patching the reject capability of new Promise(executor) to log a stack when called with a non-Error names the call site directly:
!! new Promise(…) reject() with a non-Error reason: undefined
at Object.rejectInitializing (…)
at Object.i [as connect] (…/connect.js)
at async …init (…)
Ruled out below Payload, by reading the installed source, so that the report lands in the right place:
[email protected]can only reject its connect promise with a truthy value —err ? rej(err) : res(client).[email protected]'sClient.end()with no callback returns a promise with no reject path at all.@opentelemetry/[email protected]'shandleConnectResultre-rejects the identical error.
Which area(s) are affected?
area: db-postgres
Environment Info
payload: 3.90.2
@payloadcms/db-postgres: 3.90.2
@payloadcms/drizzle: 3.90.2
pg: 8.20.0
pg-pool: 3.13.0
next: 15.4.11
node: 22.16.0 (also reproduced on 22.23.1)
- Lenguaje dominante
- TypeScript
- Estrellas
- 45.3k
- Forks
- 4.3k
- Merge medio
- 1 d 21 h
- PR fusionados (30 d)
- 272
Preparar el entorno
Inicia el contenedor de desarrollo del proyecto en tu navegador, con tu propia cuenta de GitHub.
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de payloadcms/payload
-
bug: Local API call with req resets req.fallbackLocale to defaultLocalePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertoarea: core Bug created-by: Contributor
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
payloadcms/payload#18768 ·
Los mantenedores suelen responder en 1 día
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collectionsPosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. AbiertoBug plugin: multi-tenant status: needs-triage v3
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
payloadcms/payload#18690 ·
Los mantenedores suelen responder en 1 día
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)Posiblemente ocupada @PINYOPATTANAWASANPORN la tomó hace 9 días. Abiertoarea: core Bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
payloadcms/payload#18674 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
plugin-cloud-storage: unused `find-node-modules` dependency pulls in `braces` (GHSA-vfj7-8cjw-p6xm, no patched version)Posiblemente ocupada @MokiMeow la tomó hace 12 días. AbiertoBug plugin: cloud-storage plugin: storage-* status: needs-triage v3
Dificultad 1/5 Menos de una hora Aptitud para principiantes 75/100
payloadcms/payload#18623 ·
Los mantenedores suelen responder en 1 día
-
createImageSizes decodes the source once per image size, all at once, so one upload can take hundreds of MBPosiblemente ocupada @Kshot3000 la tomó hace 15 días. Abiertoarea: core Bug Uploads
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
payloadcms/payload#18531 · 1 reacción ·
Los mantenedores suelen responder en 1 día
Todos los issues de payloadcms/payload
Issues similares
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
phivk/rubiks-lab#33 ·
Los mantenedores suelen responder en 1 día
-
BUG:Edit Album strips spaces from the password so albums with a leading/trailing space in the password can't be editedPosiblemente ocupada @ManyaJain111 la tomó hoy. Abiertobug enhancement possible-duplicate
Dificultad 2/5 1-3 horas Aptitud para principiantes 77/100
AOSSIE-Org/PictoPy#1637 · 4 comentarios ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
chaitin/MonkeyCode#1437 ·
Los mantenedores suelen responder en 1 día
-
rtdb-info-connected-order test: unhandled 'connection is closed' rejection fails the CLI suiteAbierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
area:cards enhancement migration ready-for-agent
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
fil-donadoni/tolaria#6309 ·
Los mantenedores suelen responder en 1 día