diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index da1f32659e..bd0727f445 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -44,6 +44,7 @@
 #include "storage/pg_shmem.h"
 #include "storage/shmem.h"
 #include "utils/freepage.h"
+#include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
 
@@ -751,6 +752,7 @@ dsm_attach(dsm_handle h)
 		 */
 		PG_TRY();
 		{
+			INJECTION_POINT("dsm-attach-before-mapping", NULL);
 			dsm_impl_op(DSM_OP_ATTACH, seg->handle, 0, &seg->impl_private,
 						&seg->mapped_address, &seg->mapped_size, ERROR);
 		}
diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index cc76b1bf99..ffced61211 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -110,6 +110,24 @@ RETURNS record
 AS 'MODULE_PATHNAME', 'injection_points_stats_fixed'
 LANGUAGE C STRICT;
 
+--
+-- Test-only pgstats helpers
+--
+CREATE FUNCTION injection_points_create_pgstats(IN nentries int)
+RETURNS oid
+AS 'MODULE_PATHNAME', 'injection_points_create_pgstats'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
+CREATE FUNCTION injection_points_lookup_pgstats(IN relid oid)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'injection_points_lookup_pgstats'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
+CREATE FUNCTION injection_points_test_pgstats_dsm_recovery(IN relid oid)
+RETURNS bool
+AS 'MODULE_PATHNAME', 'injection_points_test_pgstats_dsm_recovery'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- regress_injection.c functions
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index c493cfda9c..dff752ecd1 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -17,6 +17,7 @@
 
 #include "postgres.h"
 
+#include "access/xact.h"
 #include "fmgr.h"
 #include "injection_points.h"
 #include "injection_stats.h"
@@ -32,6 +33,8 @@
 #include "utils/guc.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
+#include "utils/pgstat_internal.h"
+#include "utils/resowner.h"
 #include "utils/wait_event.h"
 
 PG_MODULE_MAGIC;
@@ -46,6 +49,9 @@ PG_MODULE_MAGIC;
  */
 static List *inj_list_local = NIL;
 
+#define PGSTATS_DSM_TEST_FIRST_RELID ((Oid) 800000000)
+#define PGSTATS_DSM_TEST_INJECTION_POINT "dsm-attach-before-mapping"
+
 /*
  * Shared state information for injection points.
  *
@@ -80,6 +86,8 @@ extern PGDLLEXPORT void injection_wait(const char *name,
 									   const void *private_data,
 									   void *arg);
 
+static void injection_points_detach_by_name(char *name);
+
 /* track if injection points attached in this process are linked to it */
 static bool injection_point_local = false;
 
@@ -517,6 +525,14 @@ injection_points_detach(PG_FUNCTION_ARGS)
 {
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
+	injection_points_detach_by_name(name);
+
+	PG_RETURN_VOID();
+}
+
+static void
+injection_points_detach_by_name(char *name)
+{
 	pgstat_report_inj_fixed(0, 1, 0, 0, 0);
 	if (!InjectionPointDetach(name))
 		elog(ERROR, "could not detach injection point \"%s\"", name);
@@ -533,8 +549,106 @@ injection_points_detach(PG_FUNCTION_ARGS)
 
 	/* Remove stats entry */
 	pgstat_drop_inj(name);
+}
 
-	PG_RETURN_VOID();
+PG_FUNCTION_INFO_V1(injection_points_create_pgstats);
+Datum
+injection_points_create_pgstats(PG_FUNCTION_ARGS)
+{
+	int32		nentries = PG_GETARG_INT32(0);
+	Oid			target = InvalidOid;
+
+	if (nentries <= 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("number of pgstats entries must be positive")));
+
+	for (int32 i = 0; i < nentries; i++)
+	{
+		PgStat_EntryRef *entry_ref;
+		Oid			relid = PGSTATS_DSM_TEST_FIRST_RELID + i;
+
+		entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_RELATION,
+										 MyDatabaseId, relid, true, NULL);
+
+		if (target == InvalidOid &&
+			(entry_ref->shared_entry->body >> DSA_OFFSET_WIDTH) > 0)
+			target = relid;
+	}
+
+	if (target == InvalidOid)
+		ereport(ERROR,
+				errmsg("could not create a pgstats entry in a DSM-backed DSA segment"));
+
+	PG_RETURN_OID(target);
+}
+
+PG_FUNCTION_INFO_V1(injection_points_lookup_pgstats);
+Datum
+injection_points_lookup_pgstats(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	PgStat_EntryRef *entry_ref;
+
+	entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_RELATION,
+									 MyDatabaseId, relid, false, NULL);
+
+	PG_RETURN_BOOL(entry_ref != NULL);
+}
+
+PG_FUNCTION_INFO_V1(injection_points_test_pgstats_dsm_recovery);
+Datum
+injection_points_test_pgstats_dsm_recovery(PG_FUNCTION_ARGS)
+{
+	Oid			relid = PG_GETARG_OID(0);
+	bool		caught = false;
+	MemoryContext oldcontext = CurrentMemoryContext;
+	ResourceOwner oldowner = CurrentResourceOwner;
+
+	BeginInternalSubTransaction(NULL);
+
+	PG_TRY();
+	{
+		(void) pgstat_get_entry_ref(PGSTAT_KIND_RELATION,
+									MyDatabaseId, relid, false, NULL);
+		ReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldcontext);
+		CurrentResourceOwner = oldowner;
+	}
+	PG_CATCH();
+	{
+		ErrorData  *edata;
+
+		MemoryContextSwitchTo(oldcontext);
+		edata = CopyErrorData();
+		FlushErrorState();
+
+		RollbackAndReleaseCurrentSubTransaction();
+		MemoryContextSwitchTo(oldcontext);
+		CurrentResourceOwner = oldowner;
+
+		if (edata->message == NULL ||
+			strstr(edata->message, PGSTATS_DSM_TEST_INJECTION_POINT) == NULL)
+			ReThrowError(edata);
+
+		caught = true;
+		FreeErrorData(edata);
+	}
+	PG_END_TRY();
+
+	if (!caught)
+		ereport(ERROR,
+				errmsg("expected DSM attach injection point to raise an error"));
+
+	injection_points_detach_by_name(PGSTATS_DSM_TEST_INJECTION_POINT);
+	pgstat_request_entry_refs_gc();
+
+	if (pgstat_get_entry_ref(PGSTAT_KIND_RELATION,
+							 MyDatabaseId, relid, false, NULL) == NULL)
+		ereport(ERROR,
+				errmsg("could not find pgstats entry after DSM attach recovery"));
+
+	PG_RETURN_BOOL(true);
 }
 
 
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 2eec3d0935..27d333a35e 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -60,6 +60,7 @@ tests += {
     },
     'tests': [
       't/001_stats.pl',
+      't/002_pgstats_dsm.pl',
     ],
   },
 }
diff --git a/src/test/modules/injection_points/t/002_pgstats_dsm.pl b/src/test/modules/injection_points/t/002_pgstats_dsm.pl
new file mode 100644
index 0000000000..b86abd84e7
--- /dev/null
+++ b/src/test/modules/injection_points/t/002_pgstats_dsm.pl
@@ -0,0 +1,58 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Test recovery after an ERROR while lazily attaching a DSM segment backing
+# the pgstats DSA.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init;
+$node->append_conf(
+	'postgresql.conf', qq(
+min_dynamic_shared_memory = 0
+));
+$node->start;
+
+if (!$node->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node->safe_psql('postgres', 'CREATE EXTENSION injection_points');
+
+my $tester = $node->background_psql('postgres');
+$tester->set_query_timer_restart();
+
+# Resolve the test functions before the filler backend creates a DSM-backed
+# pgstats DSA segment that this backend has not mapped.
+$tester->query_safe("SELECT injection_points_lookup_pgstats('0'::oid)");
+$tester->query_safe(
+	"PREPARE pgstats_dsm_recovery(oid) AS SELECT injection_points_test_pgstats_dsm_recovery(\$1)"
+);
+
+$tester->query_safe('SELECT injection_points_set_local()');
+$tester->query_safe(
+	"SELECT injection_points_attach('dsm-attach-before-mapping', 'error')");
+
+my $target_relid = $node->safe_psql('postgres',
+	'SELECT injection_points_create_pgstats(10000)');
+like($target_relid, qr/^[0-9]+$/,
+	'created pgstats entry in DSM-backed DSA segment');
+
+my $result =
+  $tester->query_safe("EXECUTE pgstats_dsm_recovery('$target_relid'::oid)");
+is($result, 't', 'pgstats lookup recovers after injected DSM attach ERROR');
+
+$tester->quit;
+$node->stop;
+
+done_testing();
