@@ -112,6 +112,7 @@ try {
112112 try {
113113 await runMigrationsWithRetry ( )
114114 console . log ( 'Migrations applied successfully.' )
115+ await assertLockSessionHeld ( )
115116 await runScriptMigrations ( client )
116117 await warnOnInvalidIndexes ( )
117118 } finally {
@@ -181,17 +182,25 @@ async function acquireMigrationLock(): Promise<void> {
181182 * Replays are safe: drizzle rolls the batch back on failure, and post-COMMIT
182183 * CONCURRENTLY statements are idempotent by convention.
183184 */
185+ /**
186+ * Verify the session still holds the migration advisory lock: a changed
187+ * backend pid means the connection was recycled and the lock silently dropped.
188+ * Only sound on a direct connection — see `hasDirectMigrationUrl`.
189+ */
190+ async function assertLockSessionHeld ( ) : Promise < void > {
191+ if ( ! hasDirectMigrationUrl ) return
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+ }
199+ }
200+
184201async function runMigrationsWithRetry ( ) : Promise < void > {
185202 for ( let attempt = 1 ; ; attempt ++ ) {
186- if ( hasDirectMigrationUrl ) {
187- const [ { pid } ] = await client `SELECT pg_backend_pid() AS pid`
188- if ( pid !== lockSessionPid ) {
189- throw new Error (
190- `Database session changed mid-run (backend pid ${ lockSessionPid } -> ${ pid } ); ` +
191- 'the migration advisory lock was lost. Aborting so a fresh runner can retry safely.'
192- )
193- }
194- }
203+ await assertLockSessionHeld ( )
195204 await client . unsafe ( 'SET statement_timeout = 0' )
196205 await client . unsafe ( `SET lock_timeout = '${ DDL_LOCK_TIMEOUT } '` )
197206 try {
0 commit comments