# 3B001 — invalid_savepoint_specification

> PostgreSQL SQLSTATE 3B001: missing savepoints, transaction state, and recovery.
---

# 3B001 — invalid_savepoint_specification

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

`3B001` means that a savepoint operation named a savepoint that is absent at the current transaction level. It is different from `25P01`, which means there is no active transaction block at all.

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

| Field | Value |
| --- | --- |
| SQLSTATE | `3B001` |
| Condition | `invalid_savepoint_specification` |
| Status | `active` |
| Known present by | `8.0.0` |
| Locked snapshots | `9.0.23, 9.1.24, 9.2.24, 9.3.25, 9.4.26, 9.5.25, 9.6.24, 10.23, 11.22, 12.22, 13.23, 14.24, 15.19, 16.15, 17.11, 18.6, 19beta3` |
| Macros | `ERRCODE_S_E_INVALID_SPECIFICATION` |
| Aliases | `—` |

<!-- source facts: data/errcodes/3B001.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

The transaction manager raises `3B001` when `ROLLBACK TO SAVEPOINT` or a related savepoint operation cannot find the requested name in the current savepoint stack. PostgreSQL 18's source template includes the name; the PG10 source uses the older `no such savepoint` wording. The missing name is a transaction-structure problem, not a table-data conflict.

## Diagnosis {#diagnosis}

Capture SQLSTATE, primary message, transaction status, and the exact savepoint sequence. Check spelling, whether a savepoint was released, and whether a nested procedure or subtransaction changed the current savepoint level. After an ERROR inside an explicit block, the connection is `INERROR`; do not issue catalog or repair SQL there before rollback.

## Response {#response}

Rollback the failed block, begin a new explicit transaction, and establish a savepoint whose name and nesting are known. If partial work should survive, use a correctly named savepoint before the risky statement and `ROLLBACK TO` that existing marker; a missing marker cannot be reconstructed after the error. Treat a retry as a new transaction plan and preserve the application's normal idempotency checks.

## Messages {#messages}

18.6's fixed source has `savepoint "%s" does not exist` and a related current-level variant. The selected PG10 output is `no such savepoint`. These are source-confirmed version/call differences; branch on `3B001` and transaction state rather than a single English message.

## Representative case {#case}

`verify/cases/3B001/snippets.json` (SHA-256 `595ce7692e34d36cc31df94c65c7040ee9bc4558fa887c23b56ba3187d235deb`) contains an explicit `BEGIN`, an existing `present` savepoint, a `ROLLBACK TO SAVEPOINT missing`, and a rollback/repaired sequence. See [the public case export](../data/cases/3b001.json) and [structured evidence](../data/evidence/3b001.json).

<!-- BEGIN SQLSTATE SNIPPET: invalid_savepoint_recovery -->

```sql
BEGIN;
SAVEPOINT present;
ROLLBACK TO SAVEPOINT missing;
ROLLBACK;
BEGIN;
SAVEPOINT present;
RELEASE SAVEPOINT present;
COMMIT;
```
<!-- END SQLSTATE SNIPPET -->

The runner uses autocommit only to make each registry `BEGIN`/`ROLLBACK`/`COMMIT` boundary explicit. It asserts `INERROR` after the missing marker and `IDLE` after the recovery commit.

The selected case observed `3B001` on PostgreSQL 18.6 and 10.21. PostgreSQL 18 reported `savepoint "missing" does not exist`; PostgreSQL 10 reported `no such savepoint`. Both left the explicit block `INERROR` until `ROLLBACK`, after which a valid savepoint sequence committed.

## Versions {#versions}

The catalogue records `3B001` from its 8.0.0-or-earlier boundary through the formal snapshots. The selected 18.6 and 10.21 runs demonstrate the SQLSTATE and recovery boundary, while the primary message changed from the older generic wording to a named-savepoint template.

## Related {#related}

See [3B000 savepoint exception](../3b000/) for the class definition and [25P01 no active transaction](../25p01/) when no transaction block exists.

## Sources {#sources}

- [`src.errcodes.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/errcodes.txt) (SHA-256 `6e8de346643ba84aa3c9c6a73360acfc7b2dfb89162c06c08ce9bf5bcd5bbcba`)
- [`src.xact.18.6`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/access/transam/xact.c#L4482-L4484) (SHA-256 `75b012c0b047d1dc905a30975c244beec366e45eac0dbf109fd21bcd611a8e39`)
- [`src.xact.10.23`](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/access/transam/xact.c#L3914-L3916) (SHA-256 `9120bd418bd0f18e3f1065a3772c92ff68f3be54e1a8c6d415043197f6daa47c`)
- [`SAVEPOINT documentation`](https://www.postgresql.org/docs/18/sql-savepoint.html) · local call scan `src.calls.REL_18_6` (SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf`)
