in_hot_standby is a light, not a switch. It reports whether the server you are connected to is currently a hot standby: on when your session is riding a replica that is replaying WAL and taking read-only queries (the state hot_standby enables), off on a primary. It is a boolean, it arrived in PostgreSQL 14, and its context is none of the usual four, because nobody gets to change it:
1 postgres=# SET in_hot_standby = off;
2 ERROR: parameter "in_hot_standby" cannot be changed
3 postgres=# ALTER SYSTEM SET in_hot_standby = off;
4 ERROR: parameter "in_hot_standby" cannot be changed
Putting it in postgresql.conf earns you the same sentence in the server log at reload. It lives in the Preset Options category with block_size and the other look-but-don’t-touch parameters, and it is the only member of that category whose value can change while the server is running. Once, in one direction.
So why does a status flag get to be a GUC at all, when pg_is_in_recovery() already exists? For a human at a psql prompt, SHOW in_hot_standby is merely a nicer spelling of that function. The machinery is aimed at software, and the machinery is GUC_REPORT: a short list of parameters (server_version, client_encoding, TimeZone, and friends) that the server pushes to the client as ParameterStatus protocol messages, once at connection startup and again whenever one changes. PostgreSQL 14 added in_hot_standby (and default_transaction_read_only) to that list so libpq could learn a server’s role for free, before running a single query.
That was the point of the exercise. The same release extended target_session_attrs beyond read-write to read-only, primary, standby, and prefer-standby. Hand libpq a multi-host connection string and it walks the list, checking each server’s reported values until one qualifies:
1 psql "host=db1,db2 target_session_attrs=standby"
No probe queries on a 14-or-later server; the answer came in with the startup packet exchange. Against an older server, libpq falls back to asking out loud: SELECT pg_is_in_recovery() for the primary/standby modes, SHOW transaction_read_only for the read-write/read-only ones. The two families are not synonyms, either. read-write will reject a primary running default_transaction_read_only = on, while primary will accept it, because primary cares about recovery state and nothing else.
The one transition this parameter ever makes is promotion: on to off, never back, because turning a primary into a standby requires a restart, and the implementation leans on exactly that; the comment in guc.c reads “we rely on the assumption that it can never transition from false to true.” The part I like is when the client hears about it. The check runs as a backend is about to go idle, just before it sends ReadyForQuery, so nothing is pushed to a connection that is sitting quietly. Promote a standby under a live session and you get this sequence:
1 connected to standby ParameterStatus: in_hot_standby = on
2 pg_ctl promote
3 before any new command ParameterStatus: in_hot_standby = on (stale)
4 SHOW in_hot_standby; off (live)
5 after that ReadyForQuery ParameterStatus: in_hot_standby = off
SHOW asks shared memory directly, so it is current the instant recovery ends; the reported value your driver caches updates only when the server next talks to you, which it does not do unprompted. Note the ordering: the session became writable before the client was told it was writable. Nobody is disconnected at promotion; existing sessions simply stop being read-only, and each one finds out attached to the response of the first command it runs afterward.
That delivery schedule is the operational fact to keep. A pooler or health check that trusts its cached in_hot_standby can be stale for as long as the connection stays quiet, and target_session_attrs is evaluated at connection time only; nobody re-routes an established session. If you are building read/write routing, consume the ParameterStatus message (libpq hands it to you via PQparameterStatus()), and remember how it travels: with your next query’s response, not as breaking news. The server is glad to tell you it has been promoted. It waits for you to speak first.