Incident brief
COPY FREEZE with prior transaction activity
COPY ... WITH (FREEZE) was attempted while a cursor was still open in the same transaction. PostgreSQL refused because it can no longer guarantee no one else can see the pre-freeze rows.
What lands in your log
ERROR: cannot perform COPY FREEZE because of prior transaction activity
In 10 seconds
- What triggers it
- Start a transaction, create a new table, and open a cursor (DECLARE ... CURSOR) that is still open.
- Fix
- Run COPY ... FREEZE before opening any cursor, the table just needs to have been created or truncated in the current transaction, with no cursors or older snapshots yet.
- Proof
- Reproduced on PostgreSQL 16.14 → COPY ... WITH (FREEZE) was rejected with SQLSTATE 25000 while a cursor was still open in the same transaction. The identical COPY FREEZE succeeded once it ran before the cursor was opened.
Fix
What to do right now
Application-level steps for this error.
- Run COPY ... FREEZE before opening any cursor, the table just needs to have been created or truncated in the current transaction, with no cursors or older snapshots yet.
- Or move the cursor-based work into a separate transaction that runs after the COPY commits.
DROP TABLE IF EXISTS batch8_freeze_demo;
BEGIN;
CREATE TABLE batch8_freeze_demo (id int);
COPY batch8_freeze_demo FROM STDIN WITH (FREEZE);
1
2
.
COMMIT;
SELECT * FROM batch8_freeze_demo ORDER BY id;For this error
See this error live on the server
Run these against the affected instance to confirm the diagnosis before you act.
COPY FREEZE is valid only when the table was created or truncated in the current subtransaction and no disqualifying prior activity occurred. Run it in a fresh top-level transaction.
Current transaction and command age
Confirm you are not reusing an old transaction before starting the COPY FREEZE sequence.
SELECT txid_current_if_assigned() AS current_xid,
now() - xact_start AS transaction_age,
state, left(query, 120) AS current_query
FROM pg_stat_activity
WHERE pid = pg_backend_pid();Why it happens
What PostgreSQL is telling you
The mechanism behind the error, grounded in the official manual, not paraphrased.
PostgreSQL 16 Documentation, COPY
Rows will be frozen only if the table being loaded has been created or truncated in the current subtransaction, there are no cursors open and there are no older snapshots held by this transaction.Read the full section on postgresql.org →
COPY FREEZE with an open cursor
Per the manual, COPY FREEZE requires no cursors to be open in the transaction, an open cursor, even one that has already fetched a row, counts as prior transaction activity, so PostgreSQL refuses to freeze the rows.COPY FREEZE before opening any cursor
Running COPY FREEZE first, before the cursor is opened, satisfies the requirement, the table was created in this transaction and nothing else has happened yet, so the rows are frozen and the transaction commits cleanly.Reproduce & verify
A real, single-session PostgreSQL reproduction
A literal transcript of SQL run against a live PostgreSQL instance in an isolated lab. The commands below are exactly what was executed.
- 1Start a transaction, create a new table, and open a cursor (DECLARE ... CURSOR) that is still open.
- 2Run COPY <table> FROM STDIN WITH (FREEZE) into the newly created table.
- 3SQLSTATE 25000 is reported because a cursor is still open in the transaction.
One client, one transaction: create a table, open a cursor, then attempt COPY ... WITH (FREEZE) into that table.
-- No shared setup: each session below is its own self-contained transaction.
SELECT 'no shared schema required' AS setup_note;BEGIN;
CREATE TABLE batch8_freeze_demo (id int);
DECLARE freeze_demo_cur CURSOR FOR SELECT generate_series(1,3);
FETCH 1 FROM freeze_demo_cur;
COPY batch8_freeze_demo FROM STDIN WITH (FREEZE);
1
2
\.
ROLLBACK;BEGIN;
CREATE TABLE batch8_freeze_demo (id int);
COPY batch8_freeze_demo FROM STDIN WITH (FREEZE);
1
2
\.
DECLARE freeze_demo_cur CURSOR FOR SELECT generate_series(1,3);
FETCH 1 FROM freeze_demo_cur;
COMMIT;What PostgreSQL actually returned
setup_note
---------------------------
no shared schema required
(1 row)BEGIN
CREATE TABLE
DECLARE CURSOR
generate_series
-----------------
1
(1 row)
ERROR: cannot perform COPY FREEZE because of prior transaction activity
ROLLBACKBEGIN
CREATE TABLE
COPY 2
DECLARE CURSOR
generate_series
-----------------
1
(1 row)
COMMITThe deeper lab audit for this error
- What actually counts as "prior transaction activity": exact SQL, output, and verdict
- A manual-grounded production interpretation of the lab result
Card required. Cancel before day 7 and you are not charged.
Runbook to fix this
The runbook for this incident
Full step-by-step fixes for the condition behind this error: the diagnosis, the exact SQL, and output captured in the lab.
Connected
Everything this error touches
Every page this SQLSTATE connects to: the concept that explains it, the runbooks that fix it, the parameters you tune to prevent it, and the sibling errors it travels with. All real cross-references. Jump straight in, or open the full interactive map.
Fix it — runbooks
Related errors
Verification
- Last verified
- 2026-07-16 (Docker lab, PostgreSQL 16.14)
- Verification scope
- Verified against PostgreSQL 16.14 in an isolated lab environment
- Audit status
- reviewed
Went further?
Pro unlocks the second lab proof
Free page stops the bleeding. Pro adds the operational test, SQLSTATE audit, and deeper evidence, same error, more certainty.