# 28000 — invalid_authorization_specification

> PostgreSQL SQLSTATE 28000: invalid_authorization_specification, source-backed diagnosis and recovery guidance.
---

# 28000

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

SQLSTATE `28000` is **invalid_authorization_specification** in Class `28`. `28000` is the authorization-specification class for rejected startup or authorization context. The selected missing-role startup path sends C=`28000`, S=`FATAL`, and `role "<generated>" does not exist` before a session is established; other fixed authentication paths use this class for concrete certificate, pg_hba, and LOGIN eligibility failures.

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

| Field | Value |
| --- | --- |
| SQLSTATE | `28000` |
| Condition | `invalid_authorization_specification` |
| 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_INVALID_AUTHORIZATION_SPECIFICATION` |
| Aliases | `—` |

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

## Meaning {#meaning}

This class covers authorization context rejected while a connection is being established or authenticated. The selected branch is the startup role lookup: the server cannot create a session for the requested role and sends a FATAL ErrorResponse. Certificate, pg_hba, and LOGIN-eligibility paths remain separate producers under the same class.

The selected role lookup happens before a backend can enter a normal session. That explains both the `FATAL` severity and the absence of session-level transaction state. Do not merge it with 28P01 (password authentication failure), 42501 (permission denied after a session exists), or 3D000 (database selection); the startup phase and the server fields identify which branch you are looking at.

### Startup branch guide {#scenarios}

| What to inspect | Selected missing-role branch | Other Class 28 possibilities |
| --- | --- | --- |
| Server field | `C=28000`, `S=FATAL`, `M=role "%s" does not exist` | Preserve the server's own code and primary; class membership alone is not enough. |
| Session state | No usable session and no transaction exists on the failed attempt | A later authorization check may occur after a session exists and has a different recovery boundary. |
| Repair | Create/rename the role as intended, or correct the startup user, then connect afresh | Correct the certificate, `pg_hba.conf`, LOGIN attribute, or mapping named by the actual diagnostic. |

## Diagnosis {#diagnosis}

Use the server ErrorResponse or authentication log as SQLSTATE authority and classify the phase: role lookup, pg_hba rule, certificate, or LOGIN permission. The selected `collector` is a raw server ErrorResponse record, not csvlog/jsonlog; raw startup and psycopg are separate attempts. In the selected runs psycopg reports driver SQLSTATE `null`, the failed connection has no transaction, and two independent fresh known-role probes can still run `SELECT 1`; this is not a collector correlation claim.

For a missing role, compare the requested startup user with the role catalog from a separate administrative session and check for quoting or case-folding differences. Preserve the raw `C`, `S`, and `M` fields: psycopg's `null` is a property of its independent failed startup attempt, not evidence that the server omitted SQLSTATE. A fresh probe must use a separate connection and cannot turn the failed attempt into a transaction that can be rolled back.

## Response {#response}

Correct the role, LOGIN/mapping, certificate, or pg_hba rule identified by the server, then establish a fresh connection. There is no failed session on which to issue `ROLLBACK`; do not infer 28000 from a client exception without its server field, and do not replay non-idempotent startup work blindly.

After a role or authentication change, reconnect with the exact intended user and database and verify the resulting identity from the new session. If the application had already sent work on another connection, reconcile that work separately; the pre-session `FATAL` itself does not establish a business transaction to retry.

## Observed diagnostics {#messages}

The selected startup role branch emits `FATAL` with primary `role "%s" does not exist`; the role name is dynamic. Other authorization failures in Class 28 can use different primary text. Treat the server ErrorResponse as authoritative because the client never receives a usable session SQLSTATE.

The selected server record is `role "u28000_missing_fbc912bb7e6f" does not exist`; the generated role name is run-specific. The raw ErrorResponse and the psycopg `null` belong to separate startup attempts, so they must not be combined as if they were two views of one session.

## Representative case {#case}

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

The registry contains the probe used after the startup ErrorResponse. The trigger uses a generated role on a fresh startup connection, so no SQL statement can reproduce that pre-session failure.

```sql
SELECT 1;
```

<!-- END SQLSTATE SNIPPET -->

The selected 18.6 server ErrorResponse is C=`28000`, S=`FATAL`, `role "u28000_missing_fbc912bb7e6f" does not exist`. The driver startup diagnostic has SQLSTATE `null`; the failed connection has no transaction, while the known-good probe returned `1` and `IDLE`.

The downloadable case and evidence projections are [`28000 case JSON`](../data/cases/28000.json) and [`authored evidence`](../data/evidence/28000.json). The runner manifest is `verify/cases/28000/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}

- [`0P000` — invalid_role_specification](../0p000/)
- [`28P01` — related condition](../28p01/)
- [`3D000` — invalid_catalog_name](../3d000/)

## Sources {#sources}

- `src.miscinit-missing-role.18.6` — `src/backend/utils/init/miscinit.c` at `REL_18_6` commit `724edf9bde9d356724ad384a2e196edc3c9f80f7`; fixed blob SHA-256 `54bdb859c143a6c70d86067ccf2e124f59aa0852e5705bb653652e41a60af39a` ([source](https://github.com/postgres/postgres/blob/724edf9bde9d356724ad384a2e196edc3c9f80f7/src/backend/utils/init/miscinit.c#L802-L804)).
- `src.miscinit-missing-role.10.23` — `src/backend/utils/init/miscinit.c` at `REL_10_23` commit `02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4`; fixed blob SHA-256 `c4d2e234f96644d43aad9e9a6728df693e7f75175f05a550701413ceee70fb82` ([source](https://github.com/postgres/postgres/blob/02991e79f8f58bc208f05dcc8af0c62dbe0a6ea4/src/backend/utils/init/miscinit.c#L516-L518)).
- `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.
