ERROR: cannot execute INSERT in a read-only transaction (SQLSTATE 25006) came, in the tests here, from a transaction marked read only by SET TRANSACTION READ ONLY or by default_transaction_read_only = on. Run SELECT pg_is_in_recovery() first: if it says t you are on a standby, a case not tested here; if f, find where the setting is on and switch it off there.

Run SHOW default_transaction_read_only; and SELECT pg_is_in_recovery();. In the test the first said on and the second f, and the setting turned out to be the cause. A database-level setting needed SET default_transaction_read_only = off in the session before ALTER DATABASE ... RESET was accepted.

The error

BEGIN;
BEGIN
SET TRANSACTION READ ONLY;
SET
INSERT INTO booking VALUES (1, 'Aiko Tanaka');
psql:/tmp/f25006.sql:5: ERROR:  25006: cannot execute INSERT in a read-only transaction
LOCATION:  PreventCommandIfReadOnly, utility.c:407

The same error with the setting at database level, in a new session:

SHOW default_transaction_read_only;
 default_transaction_read_only
-------------------------------
 on
(1 row)

INSERT INTO booking VALUES (5, 'David Chen');
psql:/tmp/f25006b.sql:3: ERROR:  25006: cannot execute INSERT in a read-only transaction
LOCATION:  PreventCommandIfReadOnly, utility.c:407
ALTER DATABASE lab RESET default_transaction_read_only;
psql:/tmp/f25006b.sql:4: ERROR:  25006: cannot execute ALTER DATABASE in a read-only transaction
LOCATION:  PreventCommandIfReadOnly, utility.c:407

Why it happens

A read-only transaction refuses writes, and the message names the statement it refused: INSERT, and in the second case ALTER DATABASE too. The transaction got that mark in two ways in the test. SET TRANSACTION READ ONLY marked one transaction. ALTER DATABASE lab SET default_transaction_read_only = on made the next session on lab start read only: SHOW said on in that fresh connection.

The trap in the second case is that the repair statement is itself a write. The first ALTER DATABASE ... RESET failed with the same code, because the session that ran it was read only as well.

The fix

SET default_transaction_read_only = off;
SET
ALTER DATABASE lab RESET default_transaction_read_only;
ALTER DATABASE

A new session then showed the setting off and the insert went through:

SHOW default_transaction_read_only;
 default_transaction_read_only
-------------------------------
 off
(1 row)

INSERT INTO booking VALUES (6, 'Maria Garcia');
INSERT 0 1

For the per-transaction case, end the read-only transaction with ROLLBACK and start a new one without READ ONLY. In the test, BEGIN READ WRITE; also won over SET LOCAL default_transaction_read_only = on inside it, and the insert in that transaction committed. A server where pg_is_in_recovery() returns t is a case this test did not cover.

How it was reproduced

A postgres:18 container (PostgreSQL 18.4, Debian 18.4-1.pgdg13+1) on Docker Engine 29.9.2, database lab, table booking (id int, guest text) with fictional names. Scripts f25006.sql, f25006b.sql and f25006c.sql ran with docker exec fix8-pg3 sh -c "psql -U postgres -d lab -e -f /tmp/f25006.sql 2>&1" (and the same for the other two files), after ALTER DATABASE lab SET default_transaction_read_only = on; for the second one. \set VERBOSITY verbose at the top of the first two adds the SQLSTATE. The container name is the test's own.

Frequently asked

How do I turn off read-only mode in PostgreSQL?
In the test, SET default_transaction_read_only = off in the session, then ALTER DATABASE lab RESET default_transaction_read_only. A new session then showed off and accepted the insert.
Why does ALTER DATABASE fail with cannot execute in a read-only transaction?
The session running it was read only too. ALTER DATABASE lab RESET failed with 25006 until SET default_transaction_read_only = off ran first in the same session.
How do I see why a PostgreSQL session is read only?
Run SHOW default_transaction_read_only and SHOW transaction_read_only. Both said on in the test after SET default_transaction_read_only = on, and SELECT pg_is_in_recovery() said f.

More decoded errors in the Fixes category.