Jump to solution
Verify

The Fix

Fix race condition in adapters at startup, which caused a ProgrammingError when using UUID in multi-threaded environments. This was addressed by ensuring atomic operations when replacing class names.

Based on closed psycopg/psycopg issue #1230 · PR/commit linked

Production note: This tends to surface only under concurrency. Reproduce with load tests and watch for lock contention/cancellation paths.

Jump to Verify Open PR/Commit
@@ -10,6 +10,12 @@ --------------- +Psycopg 3.3.2 +^^^^^^^^^^^^^ +
repro.py
% mkdir /tmp/psycopg-adapter-get-dumper-race; cd /tmp/psycopg-adapter-get-dumper-race % python3.13 -m venv venv % venv/bin/pip install --quiet --force ~/src/psycopg/psycopg ~/src/psycopg/psycopg_c % cat <<EOF > race_get_dumper_uuid.py import sys import threading import uuid import psycopg def test_uuid_adapter() -> bool: conn = psycopg.connect() res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] if __name__ == "__main__": sys.setswitchinterval(0.00001) threads = [threading.Thread(target=test_uuid_adapter) for _ in range(5)] for t in threads: t.start() for t in threads: t.join() EOF % pg_virtualenv Creating new PostgreSQL cluster 18/regress ... % while ./venv/bin/python race_get_dumper_uuid.py; do : ; done ; # wait a few seconds / minutes Exception in thread Thread-4 (test_uuid_adapter): Traceback (most recent call last): File "/usr/lib/python3.13/threading.py", line 1043, in _bootstrap_inner self.run() ~~~~~~~~^^ File "/usr/lib/python3.13/threading.py", line 994, in run self._target(*self._args, **self._kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/psycopg-adapter-get-dumper-race/race_get_dumper_uuid.py", line 10, in test_uuid_adapter res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/psycopg-adapter-get-dumper-race/venv/lib/python3.13/site-packages/psycopg/connection.py", line 299, in execute raise ex.with_traceback(None) psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Apply the official fix\nFix race condition in adapters at startup, which caused a ProgrammingError when using UUID in multi-threaded environments. This was addressed by ensuring atomic operations when replacing class names.\nWhen NOT to use: Do not use this fix if your application does not involve multi-threaded environments.\n\n

Why This Fix Works in Production

  • Trigger: psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)
  • Mechanism: Race condition in get_dumper() due to non-atomic operations when replacing class names
Production impact:
  • If left unfixed, failures can be intermittent under concurrency (hard to reproduce; shows up as sporadic 5xx/timeouts).

Why This Breaks in Prod

  • Shows up under Python 3.13 in real deployments (not just unit tests).
  • Race condition in get_dumper() due to non-atomic operations when replacing class names
  • Surfaces as: psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)

Proof / Evidence

Discussion

High-signal excerpts from the issue thread (symptoms, repros, edge-cases).

“We've noticed these at program startup when using multiple threads: My guess is that the code in https://github.com/psycopg/psycopg/blob/6a24300ad2392e502a7c9d3c244ead6852890d90/psycopg/psycopg/_adapters_map.py#L218-L223 is not thread-safe,”
Issue thread · issue description · source

Failure Signature (Search String)

  • psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)

Error Message

Stack trace
error.txt
Error Message ------------- psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)
Stack trace
error.txt
Error Message ------------- % mkdir /tmp/psycopg-adapter-get-dumper-race; cd /tmp/psycopg-adapter-get-dumper-race % python3.13 -m venv venv % venv/bin/pip install --quiet --force ~/src/psycopg/psycopg ~/src/psycopg/psycopg_c % cat <<EOF > race_get_dumper_uuid.py import sys import threading import uuid import psycopg def test_uuid_adapter() -> bool: conn = psycopg.connect() res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] if __name__ == "__main__": sys.setswitchinterval(0.00001) threads = [threading.Thread(target=test_uuid_adapter) for _ in range(5)] for t in threads: t.start() for t in threads: t.join() EOF % pg_virtualenv Creating new PostgreSQL cluster 18/regress ... % while ./venv/bin/python race_get_dumper_uuid.py; do : ; done ; # wait a few seconds / minutes Exception in thread Thread-4 (test_uuid_adapter): Traceback (most recent call last): File "/usr/lib/python3.13/threading.py", line 1043, in _bootstrap_inner self.run() ~~~~~~~~^^ File "/usr/lib/python3.13/threading.py", line 994, in run self._target(*self._args, **self._kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/psycopg-adapter-get-dumper-race/race_get_dumper_uuid.py", line 10, in test_uuid_adapter res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ ... (truncated) ...

Minimal Reproduction

repro.py
% mkdir /tmp/psycopg-adapter-get-dumper-race; cd /tmp/psycopg-adapter-get-dumper-race % python3.13 -m venv venv % venv/bin/pip install --quiet --force ~/src/psycopg/psycopg ~/src/psycopg/psycopg_c % cat <<EOF > race_get_dumper_uuid.py import sys import threading import uuid import psycopg def test_uuid_adapter() -> bool: conn = psycopg.connect() res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] if __name__ == "__main__": sys.setswitchinterval(0.00001) threads = [threading.Thread(target=test_uuid_adapter) for _ in range(5)] for t in threads: t.start() for t in threads: t.join() EOF % pg_virtualenv Creating new PostgreSQL cluster 18/regress ... % while ./venv/bin/python race_get_dumper_uuid.py; do : ; done ; # wait a few seconds / minutes Exception in thread Thread-4 (test_uuid_adapter): Traceback (most recent call last): File "/usr/lib/python3.13/threading.py", line 1043, in _bootstrap_inner self.run() ~~~~~~~~^^ File "/usr/lib/python3.13/threading.py", line 994, in run self._target(*self._args, **self._kwargs) ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/psycopg-adapter-get-dumper-race/race_get_dumper_uuid.py", line 10, in test_uuid_adapter res = conn.execute("select gen_random_uuid() = %s", [uuid.uuid4()]).fetchone()[0] ~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/tmp/psycopg-adapter-get-dumper-race/venv/lib/python3.13/site-packages/psycopg/connection.py", line 299, in execute raise ex.with_traceback(None) psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)

Environment

  • Python: 3.13

What Broke

Multiple threads causing ProgrammingError when adapting UUID type at startup.

Why It Broke

Race condition in get_dumper() due to non-atomic operations when replacing class names

Fix Options (Details)

Option A — Apply the official fix

Fix race condition in adapters at startup, which caused a ProgrammingError when using UUID in multi-threaded environments. This was addressed by ensuring atomic operations when replacing class names.

When NOT to use: Do not use this fix if your application does not involve multi-threaded environments.

Fix reference: https://github.com/psycopg/psycopg/pull/1231

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • Do not use this fix if your application does not involve multi-threaded environments.

Verify Fix

verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.

Did This Fix Work in Your Case?

Quick signal helps us prioritize which fixes to verify and improve.

Prevention

  • Add a stress test that runs high-concurrency workloads and fails on thread dumps / blocked locks.
  • Enable watchdog dumps in prod (faulthandler, thread dump endpoint) to capture deadlocks quickly.

Related Issues

No related fixes found.

Sources

We don’t republish the full GitHub discussion text. Use the links above for context.