@@ -65,6 +65,16 @@ if (!url) {
6565 process . exit ( 1 )
6666}
6767
68+ /**
69+ * The backend-pid session guard below is only sound on a DIRECT connection:
70+ * through a transaction-pooling PgBouncer, consecutive statements can
71+ * legitimately run on different server backends, so a pid change does not mean
72+ * the session was lost and the guard would false-positive on every run.
73+ * MIGRATION_DATABASE_URL is by contract the direct DSN; when falling back to
74+ * DATABASE_URL (which may be pooled), the guard is skipped.
75+ */
76+ const hasDirectMigrationUrl = Boolean ( process . env . MIGRATION_DATABASE_URL )
77+
6878/**
6979 * `max_lifetime: null` is load-bearing: postgres-js defaults to recycling the
7080 * connection after a randomized 30–60 minutes, and a transparent reconnect
@@ -178,12 +188,14 @@ async function acquireMigrationLock(): Promise<void> {
178188 */
179189async function runMigrationsWithRetry ( ) : Promise < void > {
180190 for ( let attempt = 1 ; ; attempt ++ ) {
181- const [ { pid } ] = await client `SELECT pg_backend_pid() AS pid`
182- if ( pid !== lockSessionPid ) {
183- throw new Error (
184- `Database session changed mid-run (backend pid ${ lockSessionPid } -> ${ pid } ); ` +
185- 'the migration advisory lock was lost. Aborting so a fresh runner can retry safely.'
186- )
191+ if ( hasDirectMigrationUrl ) {
192+ const [ { pid } ] = await client `SELECT pg_backend_pid() AS pid`
193+ if ( pid !== lockSessionPid ) {
194+ throw new Error (
195+ `Database session changed mid-run (backend pid ${ lockSessionPid } -> ${ pid } ); ` +
196+ 'the migration advisory lock was lost. Aborting so a fresh runner can retry safely.'
197+ )
198+ }
187199 }
188200 await client . unsafe ( 'SET statement_timeout = 0' )
189201 await client . unsafe ( `SET lock_timeout = '${ DDL_LOCK_TIMEOUT } '` )
0 commit comments