diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index b51df2a4606..79ad1a24f1d 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -943,9 +943,14 @@ WaitForParallelWorkersToExit(ParallelContext *pcxt)
 		 * up safely -- we won't be able to tell when our workers are actually
 		 * dead.  This doesn't necessitate a PANIC since they will all abort
 		 * eventually, but we can't safely continue this session.
+		 *
+		 * Don't raise the FATAL (which would re-enter proc_exit()) if this is
+		 * called while the backend is already exiting; there's no session
+		 * left to end.  This can happen when AbortOutOfAnyTransaction() runs
+		 * from the ShutdownPostgres exit callback.
 		 */
 		if (status == BGWH_POSTMASTER_DIED)
-			ereport(FATAL,
+			ereport(proc_exit_inprogress ? LOG : FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("postmaster exited during a parallel transaction")));
 
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a584e138ada..42c71673de7 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -4021,8 +4021,14 @@ stop_repack_decoding_worker(void)
 		status = WaitForBackgroundWorkerShutdown(decoding_worker->handle);
 		RESUME_INTERRUPTS();
 
+		/*
+		 * Don't raise the FATAL (which would re-enter proc_exit()) if this
+		 * is called while the backend is already exiting; there's no session
+		 * left to end.  This can happen when the before_shmem_exit callback
+		 * runs during the nested proc_exit() after another FATAL error.
+		 */
 		if (status == BGWH_POSTMASTER_DIED)
-			ereport(FATAL,
+			ereport(proc_exit_inprogress ? LOG : FATAL,
 					errcode(ERRCODE_ADMIN_SHUTDOWN),
 					errmsg("postmaster exited during REPACK command"));
 	}
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 0b44a53ebe9..ff7682d79c5 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -651,19 +651,38 @@ pgstat_shutdown_hook(int code, Datum arg)
 	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
 
 	/*
-	 * If we got as far as discovering our own database ID, we can flush out
-	 * what we did so far.  Otherwise, we'd be reporting an invalid database
-	 * ID, so forget it.  (This means that accesses to pg_database during
-	 * failed backend starts might never get counted.)
+	 * Normally the transaction state has been cleaned up before we get
+	 * here, by AbortOutOfAnyTransaction() in the ShutdownPostgres exit
+	 * callback.  But if a FATAL error is raised during that cleanup --
+	 * either from a parallel context waiting for our workers, or the
+	 * exit-on-postmaster-death handling in WaitEventSetWait() --
+	 * errfinish() re-enters proc_exit() and runs the remaining exit
+	 * callbacks, this one included, with the transaction state not
+	 * cleared.  pgstat_report_stat() must not be called in that state,
+	 * so drop whatever is pending instead.
 	 */
-	if (OidIsValid(MyDatabaseId))
-		pgstat_report_disconnect(MyDatabaseId);
+	if (IsTransactionOrTransactionBlock())
+	{
+		dlist_init(&pgStatPending);
+	}
+	else
+	{
+		/*
+		 * If we got as far as discovering our own database ID, we can
+		 * flush out what we did so far.  Otherwise, we'd be reporting
+		 * an invalid database ID, so forget it.  (This means that
+		 * accesses to pg_database during failed backend starts might
+		 * never get counted.)
+		 */
+		if (OidIsValid(MyDatabaseId))
+			pgstat_report_disconnect(MyDatabaseId);
 
-	pgstat_report_stat(true);
+		pgstat_report_stat(true);
 
-	/* there shouldn't be any pending changes left */
-	Assert(dlist_is_empty(&pgStatPending));
-	dlist_init(&pgStatPending);
+		/* there shouldn't be any pending changes left */
+		Assert(dlist_is_empty(&pgStatPending));
+		dlist_init(&pgStatPending);
+	}
 
 	/* drop the backend stats entry */
 	if (!pgstat_drop_entry(PGSTAT_KIND_BACKEND, InvalidOid, MyProcNumber, false))
