An application that sets search_path once at connection startup and expects every later request to keep it may conflict with transaction pooling. The client connection and backend allocation have different lifetimes. This does not by itself establish an observed cross-user schema leak.
Do not assume one backend across two transactions
In a hypothetical case, client A’s first transaction uses server connection 1 and a later transaction receives connection 2. State left only on the first connection is not guaranteed on the second. The session/transaction distinction in PgBouncer’s feature map requires the application to cooperate with the pooling contract.
BEGIN;
SET LOCAL search_path = app_schema, pg_catalog;
SELECT current_schema();
ROLLBACK;
This transaction-scoped example uses your reviewed schema name. PostgreSQL SET LOCAL lasts until that transaction ends. The business queries must be inside the same transaction; do not concatenate arbitrary user input into a schema identifier. Explicit schema-qualified table names are another option for static queries.
Keep prepared-statement behavior separate
It is inaccurate to say that every prepared statement is impossible in transaction mode. Protocol-level named prepared statements differ from SQL PREPARE; check the PgBouncer version, max_prepared_statements and driver behavior together. That feature does not guarantee arbitrary session state such as search_path.
In staging, verify schema selection across repeated transactions from two different clients. One successful SELECT is insufficient; observe transaction boundaries, rollback after errors and backend reuse. The SQL here is documentation-based and was not run against a production pool.