# 25P03 — idle_in_transaction_session_timeout

> PostgreSQL SQLSTATE 25P03: diagnosing and recovering from server termination of a session idle inside an open transaction.
---

# 25P03 — idle_in_transaction_session_timeout

## At a glance {#at-a-glance}

`25P03` is a FATAL session-termination condition. It fires when a session is waiting for the next client query inside an open transaction longer than the effective `idle_in_transaction_session_timeout`. The selected case uses 300 ms as a harness trigger, not as a production recommendation.

<!-- BEGIN SQLSTATE FACTS: generated by scripts/generate.py; do not edit -->

| Field | Value |
| --- | --- |
| SQLSTATE | `25P03` |
| Condition | `idle_in_transaction_session_timeout` |
| Status | `active` |
| Known present by | `9.6.0` |
| Locked snapshots | `9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT` |
| Aliases | `—` |

<!-- source facts: data/errcodes/25P03.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

This timeout protects the server from sessions that keep an open transaction while waiting for the client; it can retain locks and delay cleanup. It applies while the session is idle in the transaction, including the `idle in transaction` and `idle in transaction (aborted)` states, not while an active statement is running. `statement_timeout` cancels a statement, while `transaction_timeout` covers the total transaction lifetime on versions that support it. The selected run covers the non-aborted `INTRANS` case only.

## Diagnosis {#diagnosis}

Before the session disappears, capture its effective `SHOW idle_in_transaction_session_timeout` (or the matching `pg_settings` row) on that timed session itself. A control connection's `SHOW` or `pg_settings` describes the observer backend and cannot establish another session's effective `SET` value. From the control connection, inspect `pg_stat_activity` fields such as `pid`, `usename`, `application_name`, `client_addr`, `state`, `xact_start`, `state_change`, `query_start`, and `query` for `state IN ('idle in transaction', 'idle in transaction (aborted)')`. If the state is already aborted, preserve the earlier root error and 25P02 as separate diagnostics; the selected run is the non-aborted `INTRANS` path. These identify the pool or application path and how long it has been idle; permissions can limit what an observer sees. Correlate the FATAL record by backend PID, SQLSTATE, `error_severity = FATAL`, and exact message. The selected psycopg/libpq stack surfaced 25P03 in the driver diagnostic and CSV; PG18 also supplied JSON. The original connection is closed, so its transaction status cannot be repaired with `ROLLBACK`.

## Response {#response}

Treat the session as gone: discard it from the pool and reconnect before retrying idempotent work. Prevent recurrence by committing or rolling back before returning a connection to the pool, and by fixing the application path that leaves a transaction open. Do not lower this timeout to suppress the error; a shorter value makes termination more likely. Choose an effective value above legitimate idle periods, or set it to `0` only when the deployment deliberately accepts the lock and cleanup risk. When the server closes this session, it rolls back the open incomplete transaction before exiting; the dead connection cannot accept a later `ROLLBACK`. A known uncommitted transaction on this terminated session cannot be recovered through that connection. If a separate network failure occurred after a `COMMIT` may have been sent, reconcile the business result from a fresh connection before retrying; that uncertainty is not implied by every 25P03 FATAL.

## FATAL message {#messages}

The server source emits `terminating connection due to idle-in-transaction timeout` at `FATAL` severity; the original connection is terminated.

## Observed diagnostics {#observed}

`18.6 (Homebrew) / latest`: FATAL SQLSTATE `25P03`; primary `terminating connection due to idle-in-transaction timeout`; backend PID `26447`; original connection closed `True`; collector CSV `25P03`; collector JSON `25P03`; fresh probe `1`.
`10.21 (Debian 10.21-1.pgdg90+1) / pg10`: FATAL SQLSTATE `25P03`; primary `terminating connection due to idle-in-transaction timeout`; backend PID `81`; original connection closed `True`; collector CSV `25P03`; collector JSON `not applicable`; fresh probe `1`.

## Representative case {#case}

The SQL excerpt below is not a paste-all script: run the setting, PID, and `BEGIN` on a test connection, then stop sending it queries while a separate observer or log collector waits for FATAL; after the old connection is terminated, open a fresh owner connection for the final probe. The runner reads these statements from the shared registry; complete assertions, collector correlation, environment, and cleanup are in [case export](../data/cases/25p03.json).

<!-- BEGIN SQLSTATE SNIPPET: idle_in_transaction_timeout -->
```sql
-- set_timeout
SET idle_in_transaction_session_timeout = '300ms';
-- backend_pid
SELECT pg_backend_pid();
-- begin
BEGIN;
-- probe
SELECT 1;
```
<!-- END SQLSTATE SNIPPET -->

The SQLSTATE, diagnostics, states, and repair assertions for this excerpt come from the shared registry (SHA-256 `95fd079c8bca77c6e3ffc398e404a218e4f810e5fa36e30127cbc6470a6d6eb2`); [structured evidence](../data/evidence/25p03.json).

Authored evidence IDs: `identity`, `timeout-path`, `runtime`. Selected runtime records: `runtime.25P03-batch2c-latest-20260909.latest`, `runtime.25P03-batch2c-pg10-20260909.pg10`.
## Versions and limits {#versions}

The selected 300 ms termination case passes on PostgreSQL 18.6 and 10.21. PG18 has CSV and JSON collector records; PG10 has CSV only under its configured logging target. Both fresh owner connections run `SELECT 1` successfully. The timeout value is deployment-specific; the case does not prescribe it.

## Related {#related}

[25P04 transaction timeout](../25p04/), [25006 read-only SQL transaction](../25006/), [57014 query canceled](../57014/).

## Sources {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt) (SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`)
- `src.calls.REL_18_6` — `raw/calls/REL_18_6.jsonl` (SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`)
- [`src.postgres.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L3473-L3501) (SHA-256 `9fb62275b1badf94d01ab351337b60410cd9b3ab1fe63fa9f23d6d2185a21061`)
- [`src.config.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/config.sgml#L10252-L10347) (SHA-256 `ffca322c15315ba670733d6b320f23cbba7374db0d18937298ffc07b58799ef6`) · [official documentation](https://www.postgresql.org/docs/18/runtime-config-client.html)
- [`doc.monitoring.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/monitoring.sgml#L706-L875) (SHA-256 `06b147f8d966fe46821871703ffcee8a3449cc3f7de5b64b9177f48569fe96f6`) · [official monitoring documentation](https://www.postgresql.org/docs/18/monitoring-stats.html)
- [`src.system_views.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/catalog/system_views.sql#L604-L605) (SHA-256 `418f4ae7472fe0742e6fbdc7a055581602c5131e082aa6f17e2c7269bf711972`) · [official `pg_settings` documentation](https://www.postgresql.org/docs/18/view-pg-settings.html)
- [`doc.protocol.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/doc/src/sgml/protocol.sgml#L61-L65) (SHA-256 `745339e07ebbc2bb67d98b258e25772ada644ef26011e3cd6b3fcbb435235f89`) · [official protocol documentation](https://www.postgresql.org/docs/18/protocol.html)
