db-postgres: connect() rejects its `initializing` promise with no argument, producing an unhandledRejection with reason `undefined`
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 76/100
- issue の種類
- バグ
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
- 技術スタック
- node.js, postgresql, typescript
調査の方向性
packages/db-postgres/src/index.ts と packages/db-postgres/src/connect.ts を読み、次に packages/drizzle/src/types.ts の rejectInitializing の型と、packages/drizzle/src/transactions/beginTransaction.ts の利用箇所を確認してください。issue では2つの修正方法が示されています。接続エラーがどのように promise に伝わるか、また beginTransaction が拒否をどのように処理するかを追ってください。孤立した拒否で undefined が報告されなくなり、接続エラーは引き続き表面化する状態が完了の条件です。
索引モデルが issue の本文から書いたものです。
説明
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)
- 主要言語
- TypeScript
- スター
- 45.3k
- フォーク
- 4.3k
- 平均マージ
- 2日 18分
- マージ済み PR(30日)
- 293
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
payloadcms/payload のほかの issue
-
WhereBuilder throws "Cannot read properties of undefined (reading 'value')" when the list URL filters on a field that is not filterable対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンarea: ui Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
payloadcms/payload#19002 ·
メンテナーはふだん 1 日以内に返信
-
Regression: `findByID` with `disableErrors: true` throws `NotFound` for null/empty ids since 3.91.0対応中かも @Krrishkumar125 が 2 日前に担当しました。 オープンarea: core Bug invalid-reproduction status: needs-triage v3
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
payloadcms/payload#18935 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
bug: Local API call with req resets req.fallbackLocale to defaultLocale対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンarea: core Bug created-by: Contributor
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
payloadcms/payload#18768 ·
メンテナーはふだん 1 日以内に返信
-
[plugin-multi-tenant] Infinite redirect loop with custom tenantFieldName and isGlobal collections対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンBug plugin: multi-tenant status: needs-triage v3
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
payloadcms/payload#18690 ·
メンテナーはふだん 1 日以内に返信
-
Global update drops overrideAccess, so Local API updateGlobal fails filterOptions validation (3.90 regression)対応中かも @PINYOPATTANAWASANPORN が 10 日前に担当しました。 オープンarea: core Bug
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
payloadcms/payload#18674 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
payloadcms/payload の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 66/100
lyswhut/lx-music-desktop#3026 ·
-
area: ci type: test
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
schlessera/brain-kit#2769 ·
メンテナーはふだん 1 日以内に返信
-
Read a vector index's quantization back on Neo4j 2026.10, which reports vector.quantization.typeオープン
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
liberation-data/drivine#231 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
alveusgg/alveusgg#2502 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
メンテナーはふだん 1 日以内に返信