# 08P01 — protocol_violation

> PostgreSQL SQLSTATE 08P01: protocol_violation, source-backed diagnosis and recovery guidance.
---

# 08P01

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

SQLSTATE `08P01` is **protocol_violation** in Class `08`. `08P01` means that a frontend/backend protocol message violated the wire contract. The selected raw TCP case completes startup and observes ReadyForQuery (`Z`), sends an unknown frontend byte `Y` in a valid frame, receives C=`08P01`, S=`FATAL`, primary `invalid frontend message type 89`, and reads server EOF before the client closes the socket.

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

| Field | Value |
| --- | --- |
| SQLSTATE | `08P01` |
| Condition | `protocol_violation` |
| Status | `active` |
| Known present by | `7.4` |
| 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_PROTOCOL_VIOLATION` |
| Aliases | `—` |

<!-- source facts: data/errcodes/08P01.json -->
<!-- END SQLSTATE FACTS -->

## Meaning {#meaning}

The server protocol parser accepts the startup exchange, then rejects an unknown frontend message type before it can dispatch SQL. In the selected path this is a FATAL `invalid frontend message type 89`, followed by server EOF; the code is therefore a synchronization boundary for the affected socket, not a SQL parser error.

The important boundary is the wire state, not the SQL text. A client can reach `ReadyForQuery` and still fail on its next message type, as the selected case does; a malformed startup frame or a proxy that changes message framing can fail even earlier and may leave no usable session. The selected runtime exercises the unknown-message dispatch branch; the fixed source evidence below also covers distinct Bind and message-buffer parser branches, while other transport failures must be identified from their own ErrorResponse or log evidence.

`08P01` does not imply one severity or one socket outcome. In the fixed extended-query path, a Bind message with a mismatched parameter-format count or parameter count reports `ERROR`; the top-level loop aborts the current command and waits for a protocol-valid `Sync` before sending the next `ReadyForQuery`. The fixed message-buffer readers likewise use `ERROR` for missing bytes, invalid strings, and trailing data. Those recoverable protocol errors are distinct from an unknown message type, which is a `FATAL` boundary because the server can no longer trust message synchronization.

## Diagnosis {#diagnosis}

Inspect driver/proxy protocol version, message type, frame length, startup mode, and connection ownership. The `collector` recorded in the selected evidence is a raw protocol ErrorResponse collector (C/S/M plus server EOF), not csvlog/jsonlog. The FATAL socket is closed by the server and is never reused; a separate runner-owned connection returns `SELECT 1` and `IDLE`. This is a wire-protocol boundary, not SQL syntax or a client-created SQLSTATE.

Use the phase to interpret recovery:

- Before startup completes, there is no session-level transaction and the driver may only have a connection exception. Preserve the raw server response if one was sent.
- After startup reaches `Z`, an unknown frontend message is rejected by the established backend, but the selected branch is `FATAL`; the server then closes that socket. There is no `ROLLBACK` to send on it, even if the client had begun work before the protocol desynchronization.
- On a pool or proxy path, compare the bytes sent by the owner with the protocol version and framing expected by the server. A fresh `SELECT 1` on another connection proves reachability only; it does not prove that an in-flight request committed.

Use the primary text to identify the branch before choosing a recovery action. `bind message has ... parameter formats` and `bind message supplies ... parameters` point to extended-query cardinality; `no data left in message`, `insufficient data left in message`, `invalid string in message`, and `invalid message format` point to a malformed message body or boundary. These source-defined `ERROR` variants may be followed by `Sync` recovery when the driver preserves framing. `invalid frontend message type ...` with `FATAL` means the selected socket is already lost.

## Response {#response}

For a `FATAL` response or server EOF, close and discard the affected socket, correct the protocol or proxy framing, and reconnect. For an extended-query `ERROR`, let the driver send protocol-valid `Sync` and wait for `ReadyForQuery`, then inspect the transaction status; `Sync` does not roll back a failed explicit transaction, so status `E` still requires `ROLLBACK` or an intentional savepoint recovery. If a non-idempotent request was in flight, reconcile its result before replaying it; the independent probe proves only that a fresh connection works.

Treat the selected case as a session-termination branch: after server EOF, establish a fresh connection and repeat only a reconciled, idempotent operation. If the violation happened before a session existed, fix the startup/proxy configuration first. A client-side parser exception or a socket close without the server's `C` field is insufficient to label the event `08P01`.

Do not improvise a byte sequence or treat the transaction as successfully committed. If the driver cannot preserve the message boundary or does not implement the Sync contract, discard the connection and reconcile the request. This source-only flow does not change the selected unknown-byte runtime, whose `FATAL` socket must be discarded.

## Messages {#messages}

The fixed parser branch emits `FATAL` with primary `invalid frontend message type %d`; the numeric byte is dynamic. The selected byte `Y` is reported as `89`. The raw ErrorResponse, not a client exception, is the SQLSTATE authority.

Other fixed source branches emit `ERROR` with these primary templates:

- `bind message has %d parameter formats but %d parameters`
- `bind message supplies %d parameters, but prepared statement "%s" requires %d`
- `no data left in message`
- `insufficient data left in message`
- `invalid string in message`
- `invalid message format`

These Bind and message-buffer variants are source-only in this page; they are not additional runtime observations and do not all terminate the socket.

The selected wire sequence is: startup exchange → `Z` ReadyForQuery → valid frame with type byte `Y` (decimal `89`) → ErrorResponse `C=08P01`, `S=FATAL`, `M=invalid frontend message type 89` → server EOF. The collector captures those protocol fields and EOF directly; it is neither a csvlog/jsonlog record nor the later independent probe.

## Representative case {#case}

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

The SQL probe is the independent connection check. The actual trigger is a runner-owned TCP handshake followed by an unknown frontend message byte; it cannot be represented by SQL alone.

```sql
SELECT 1;
```

<!-- END SQLSTATE SNIPPET -->

The selected 18.6 server record is C=`08P01`, S=`FATAL`, primary `invalid frontend message type 89`; startup reached `Z`, the server EOF assertion was `True`, and the separate probe returned `1` and `IDLE`. The raw trigger connection is therefore closed and never reused.

The downloadable case and evidence projections are [`08P01 case JSON`](../data/cases/08p01.json) and [`authored evidence`](../data/evidence/08p01.json). The runner manifest is `verify/cases/08P01/cases.json`; the page SQL is checked against its shared registry before publication.

## Versions {#versions}

The generated facts table records the locked catalogue snapshots and earliest observed definition. The selected natural runtime scope is PostgreSQL 18.6 and 10.21; this does not infer behavior for every intermediate release.

## Related {#related}

- [`08001` — sqlclient_unable_to_establish_sqlconnection](../08001/)
- [`28000` — invalid_authorization_specification](../28000/)

## Sources {#sources}

- `src.protocol-invalid-frontend.18.6` — `src/backend/tcop/postgres.c` at `REL_18_6` commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; fixed blob SHA-256 `9fb62275b1badf94d01ab351337b60410cd9b3ab1fe63fa9f23d6d2185a21061` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/tcop/postgres.c#L454-L456)).
- `src.protocol-invalid-frontend.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L430-L432)).
- `src.postgres-bind.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L1483-L1580)).
- `src.pqformat.10.23` — `src/backend/libpq/pqformat.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `637923dbf2b9d9a0610350784f3b1d7d36a545cd8a6bd2cbe6272d9549873a91` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/libpq/pqformat.c#L432-L682)).
- `src.postgres-sync.10.23` — `src/backend/tcop/postgres.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `badfe30749794afa80a799dcdbd142fd4e4732ac1c913002d365549ae5313497` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/tcop/postgres.c#L3863-L3980)).
- `src.calls.REL_18_6` / `src.calls.REL_10_23` — fixed local call scans, SHA-256 `9ee8a0e81d8f0825c5c1ae45583439859a26e602bdd4ce2f2a62aa278867ccbf` / `00d16d3eb01b71ccf1b245c8f3102f9d0ec9f36fb02777b8dd1b99fcb263040c`; these scans preserve the resolved call context used by the claims.
