--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -436,6 +436,7 @@
 static void hashagg_spill_init(HashAggSpill *spill, LogicalTapeSet *tapeset,
 							   int used_bits, double input_groups,
 							   double hashentrysize);
+static void hashagg_init_spill_slot(AggState *aggstate);
 static pg_noinline Size hashagg_spill_tuple(AggState *aggstate,
 											 HashAggSpill *spill,
 											 TupleTableSlot *inputslot,
@@ -3054,6 +3055,40 @@
 }
 
 /*
+ * Initialize the compact spill slot on first use.
+ * Use the query context so it survives tuple resets and rescans.
+ */
+static void
+hashagg_init_spill_slot(AggState *aggstate)
+{
+	EState	   *estate = aggstate->ss.ps.state;
+	TupleDesc	scanDesc = aggstate->hash_spill_rslot->tts_tupleDescriptor;
+	MemoryContext oldcontext;
+	int			natts = bms_num_members(aggstate->colnos_needed);
+	TupleDesc	spilldesc;
+	int			spillattno = 0;
+	int			colno = -1;
+
+	oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
+
+	spilldesc = CreateTemplateTupleDesc(natts);
+	aggstate->hash_spill_colmap = palloc_array(int, natts);
+	while ((colno = bms_next_member(aggstate->colnos_needed, colno)) > 0)
+	{
+		aggstate->hash_spill_colmap[spillattno] = colno - 1;
+		TupleDescCopyEntry(spilldesc, ++spillattno, scanDesc, colno);
+	}
+	TupleDescFinalize(spilldesc);
+	aggstate->hash_spill_wslot =
+		ExecInitExtraTupleSlot(estate, spilldesc, &TTSOpsMinimalTuple);
+	/* Keep omitted input columns NULL. */
+	memset(aggstate->hash_spill_rslot->tts_isnull, true,
+		   scanDesc->natts * sizeof(bool));
+
+	MemoryContextSwitchTo(oldcontext);
+}
+
+/*
  * hashagg_spill_tuple
  *
  * No room for new groups in the hash table. Save for later in the appropriate
@@ -3071,9 +3106,13 @@
 	/* spill only attributes that we actually need, in column order */
 	if (!aggstate->all_cols_needed)
 	{
-		int		   *colmap = aggstate->hash_spill_colmap;
+		int		   *colmap;
 		int			natts;
 
+		if (aggstate->hash_spill_wslot == NULL)
+			hashagg_init_spill_slot(aggstate);
+
+		colmap = aggstate->hash_spill_colmap;
 		spillslot = aggstate->hash_spill_wslot;
 		natts = spillslot->tts_tupleDescriptor->natts;
 		slot_getsomeattrs(inputslot, aggstate->max_colno_needed);
@@ -3763,28 +3802,6 @@
 
 		aggstate->hash_spill_rslot = ExecInitExtraTupleSlot(estate, scanDesc,
 															&TTSOpsMinimalTuple);
-
-		if (!aggstate->all_cols_needed)
-		{
-			int			natts = bms_num_members(aggstate->colnos_needed);
-			TupleDesc	spilldesc;
-			int			spillattno = 0;
-			int			colno = -1;
-
-			spilldesc = CreateTemplateTupleDesc(natts);
-			aggstate->hash_spill_colmap = palloc_array(int, natts);
-			while ((colno = bms_next_member(aggstate->colnos_needed, colno)) > 0)
-			{
-				aggstate->hash_spill_colmap[spillattno] = colno - 1;
-				TupleDescCopyEntry(spilldesc, ++spillattno, scanDesc, colno);
-			}
-			TupleDescFinalize(spilldesc);
-			aggstate->hash_spill_wslot = ExecInitExtraTupleSlot(estate, spilldesc,
-																&TTSOpsMinimalTuple);
-			/* Unspilled columns are never used from and stay NULL. */
-			memset(aggstate->hash_spill_rslot->tts_isnull, true,
-				   scanDesc->natts * sizeof(bool));
-		}
 
 		/* Skip massive memory allocation if we are just doing EXPLAIN */
 		if (!(eflags & EXEC_FLAG_EXPLAIN_ONLY))
