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.
@@ -10,6 +10,12 @@
---------------
+Psycopg 3.3.2
+^^^^^^^^^^^^^
+
% 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)
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
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
- 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
- GitHub issue: #1230
- Fix PR: https://github.com/psycopg/psycopg/pull/1231
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.70
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.33
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,”
Failure Signature (Search String)
- psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)
Error Message
Stack trace
Error Message
-------------
psycopg.ProgrammingError: cannot adapt type 'UUID' using placeholder '%s' (format: AUTO)
Stack trace
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
% 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.
Fix reference: https://github.com/psycopg/psycopg/pull/1231
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- Do not use this fix if your application does not involve multi-threaded environments.
Verify Fix
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.