Jump to solution
Verify

The Fix

pip install urllib3==2.4.0

Based on closed urllib3/urllib3 issue #3567 · PR/commit linked

Production note: Watch p95/p99 latency and retry volume; timeouts can turn into retry storms and duplicate side-effects.

Jump to Verify Open PR/Commit
@@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@ +Updated exceptions to save and restore more properties +during the pickle/serialization process. \ No newline at end of file
repro.py
>> import platform >> import ssl >> import urllib3 >> >> print("OS", platform.platform()) print("Python", platform.python_version()) print(ssl.OPENSSL_VERSION) print("urllib3", urllib3.__version__) OS macOS-15.3.1-x86_64-i386-64bit >> print("Python", platform.python_version()) Python 3.10.14 >> print(ssl.OPENSSL_VERSION) OpenSSL 1.1.1w 11 Sep 2023 >> print("urllib3", urllib3.__version__) urllib3 2.3.0
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
Option A — Upgrade to fixed release\npip install urllib3==2.4.0\nWhen NOT to use: This fix is not suitable if maintaining original exception behavior is critical.\n\n

Why This Fix Works in Production

  • Trigger: E requests.exceptions.ReadTimeout: None: oh-no!
  • Mechanism: ReadTimeoutError loses context during pickling in multiprocessing
  • Why the fix works: Updated exceptions to save and restore more properties during the pickle/serialization process. (first fixed release: 2.4.0).
Production impact:
  • If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).

Why This Breaks in Prod

  • Shows up under Python 3.10.14 in real deployments (not just unit tests).
  • ReadTimeoutError loses context during pickling in multiprocessing
  • Surfaces as: E requests.exceptions.ReadTimeout: None: oh-no!

Proof / Evidence

  • GitHub issue: #3567
  • Fix PR: https://github.com/urllib3/urllib3/pull/3572
  • First fixed release: 2.4.0
  • Reproduced locally: No (not executed)
  • Last verified: 2026-02-09
  • Confidence: 0.85
  • Did this fix it?: Yes (upstream fix exists)
  • Own content ratio: 0.69

Discussion

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

“To give a tiny bit more context, my app is actually using requests, but it saves the urllib3 ReadTimeoutError then wraps it like so: https://github.com/psf/requests/blob/main/src/requests/adapters.py#L713”
@csm10495 · 2025-02-27 · source
“FYI, ex.url will be pickled https://github.com/urllib3/urllib3/blob/65d7b5fdc332f9f51aad2f119fae683eb080919d/src/urllib3/exceptions.py#L48-L50 I guess a passed message was not added to the pickled data because it was not retrievable from an…”
@illia-v · 2025-02-27 · source
“https://github.com/urllib3/urllib3/blob/b20c8368ec02421ecc4e49d796cd5eff1e04f325/src/urllib3/exceptions.py#L29-L39 @csm10495 we can save str(self) instead of self._message in __reduce__”
@illia-v · 2025-11-04 · source
“@illia-v, I came up with yet another way to better handle this. Filed: https://github.com/urllib3/urllib3/issues/3707. We can pick up over there. Thanks!”
@csm10495 · 2025-11-10 · source

Failure Signature (Search String)

  • E requests.exceptions.ReadTimeout: None: oh-no!

Error Message

Stack trace
error.txt
Error Message ------------- E requests.exceptions.ReadTimeout: None: oh-no!

Minimal Reproduction

repro.py
>> import platform >> import ssl >> import urllib3 >> >> print("OS", platform.platform()) print("Python", platform.python_version()) print(ssl.OPENSSL_VERSION) print("urllib3", urllib3.__version__) OS macOS-15.3.1-x86_64-i386-64bit >> print("Python", platform.python_version()) Python 3.10.14 >> print(ssl.OPENSSL_VERSION) OpenSSL 1.1.1w 11 Sep 2023 >> print("urllib3", urllib3.__version__) urllib3 2.3.0

Environment

  • Python: 3.10.14
  • urllib3: 2.3.0

What Broke

Exceptions raised in multiprocessing lose important context, leading to confusion.

Why It Broke

ReadTimeoutError loses context during pickling in multiprocessing

Fix Options (Details)

Option A — Upgrade to fixed release Safe default (recommended)

pip install urllib3==2.4.0

When NOT to use: This fix is not suitable if maintaining original exception behavior is critical.

Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.

Option D — Guard side-effects with OnceOnly Guardrail for side-effects

Mitigate duplicate external side-effects under retries/timeouts/agent loops by gating the operation before calling external systems.

  • Place OnceOnly between your code/agent and real side-effects (Stripe, emails, CRM, APIs).
  • Use a stable key per side-effect (e.g., customer_id + action + idempotency_key).
  • Fail-safe: configure fail-open vs fail-closed based on blast radius and spend risk.
  • This does NOT fix data corruption; it only prevents duplicate side-effects.
Show example snippet (optional)
onceonly.py
from onceonly import OnceOnly import os once = OnceOnly(api_key=os.environ["ONCEONLY_API_KEY"], fail_open=True) # Stable idempotency key per real side-effect. # Use a request id / job id / webhook delivery id / Stripe event id, etc. event_id = "evt_..." # replace key = f"stripe:webhook:{event_id}" res = once.check_lock(key=key, ttl=3600) if res.duplicate: return {"status": "already_processed"} # Safe to execute the side-effect exactly once. handle_event(event_id)

See OnceOnly SDK

When NOT to use: Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

Fix reference: https://github.com/urllib3/urllib3/pull/3572

First fixed release: 2.4.0

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

  • This fix is not suitable if maintaining original exception behavior is critical.
  • Do not use this to hide logic bugs or data corruption. Use it to block duplicate external side-effects and enforce tool permissions/spend caps.

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 CI check that diffs key outputs after upgrades (OpenAPI schema snapshots, JSON payload shapes, CLI output).
  • Upgrade behind a canary and run integration tests against the canary before 100% rollout.
  • Add a TLS smoke test that performs a real handshake in CI (include CA bundle validation and hostname checks).
  • Alert on handshake failures by error string and endpoint to catch cert/CA changes quickly.

Version Compatibility Table

VersionStatus
2.4.0 Fixed

Related Issues

No related fixes found.

Sources

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