Jump to solution
Verify

The Fix

Upgrade to version 0.7.2 or later.

Based on closed encode/httpx issue #825 · PR/commit linked

Production note: Most teams hit this during upgrades or environment changes. Roll out with a canary and smoke critical endpoints (health, OpenAPI/docs) before 100%.

Jump to Verify Open PR/Commit
@@ -1,9 +1,17 @@ import mimetypes import os +import re import typing from io import BytesIO
repro.py
$ cat reproducer.py import asyncio import httpx async def mdk(): async with httpx.AsyncClient() as client: r = await client.get('https://www.example.org/') print(r.text) asyncio.run(mdk()) $ python reproducer.py TRACE [2020-02-26 09:37:29] httpx._config - load_ssl_context verify=True cert=None trust_env=True http2=False TRACE [2020-02-26 09:37:29] httpx._config - load_verify_locations cafile=/home/mdk/clones/python/docsbuild-scripts/.venv/lib/python3.8/site-packages/certifi/cacert.pem TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='www.example.org' port=443) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.example.org' port=443)) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - start_connect tcp host='www.example.org' port=443 timeout=Timeout(timeout=5.0) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - connected http_version='HTTP/1.1' TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_headers method='GET' target='/' headers=Headers({'host': 'www.example.org', 'user-agent': 'python-httpx/0.11.1', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'}) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_data data=Data(<0 bytes>) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=NEED_DATA TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'content-encoding', b'gzip'), (b'accept-ranges', b'bytes'), (b'age', b'499617'), (b'cache-control', b'max-age=604800'), (b'content-type', b'text/html; charset=UTF-8'), (b'date', b'Wed, 26 Feb 2020 08:37:29 GMT'), (b'etag', b'"3147526947"'), (b'expires', b'Wed, 04 Mar 2020 08:37:29 GMT'), (b'last-modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'server', b'ECS (bsa/EB17)'), (b'vary', b'Accept-Encoding'), (b'x-cache', b'HIT'), (b'content-length', b'648')], http_version=b'1.1', reason=b'OK') DEBUG [2020-02-26 09:37:29] httpx._client - HTTP Request: GET https://www.example.org/ "HTTP/1.1 200 OK" TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=Data(<648 bytes>) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=EndOfMessage(headers=[]) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - response_closed our_state=DONE their_state=DONE TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.example.org' port=443)) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - close_connection TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_event event=ConnectionClosed() <!doctype html> [... redacted (useless html) ...] /home/mdk/.local/lib/python3.8/asyncio/selector_events.py:694: ResourceWarning: unclosed transport <_SelectorSocketTransport fd=6> _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) Object allocated at (most recent call last): File "reproducer.py", lineno 10 asyncio.run(mdk()) File "/home/mdk/.local/lib/python3.8/asyncio/runners.py", lineno 43 return loop.run_until_complete(main) File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 603 self.run_forever() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 570 self._run_once() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 1859 handle._run() File "/home/mdk/.local/lib/python3.8/asyncio/ ... (truncated) ...
verify
Re-run: ipython
fix.md
Option A — Upgrade to fixed release\nUpgrade to version 0.7.2 or later.\nWhen NOT to use: Do not use if it changes public behavior or if the failure cannot be reproduced.\n\n

Why This Fix Works in Production

  • Trigger: $ ipython
  • Mechanism: Fixed multipart header params encoding, resolving issues with non-ASCII letters in multipart/form-data requests.
  • Why the fix works: Fixed multipart header params encoding, resolving issues with non-ASCII letters in multipart/form-data requests. (first fixed release: 0.7.2).
Production impact:
  • If left unfixed, the same config can fail only in production (env differences), causing startup failures or partial feature outages.

Why This Breaks in Prod

  • Shows up under Python 3.8.0 in real deployments (not just unit tests).
  • Surfaces as: $ ipython

Proof / Evidence

  • GitHub issue: #825
  • Fix PR: https://github.com/encode/httpcore/pull/167
  • First fixed release: 0.7.2
  • 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.26

Discussion

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

“I'm changing the title appropriately, as the "An open stream ..." has been fixed in Python 3.8.1, this discussion is about the ResourceWarning.”
@JulienPalard · 2020-02-26 · confirmation · source
“Raised https://bugs.python.org/issue39758”
@lovelydinosaur · 2020-02-26 · source
“Tried with HTTPX_LOG_LEVE=trace because tracemalloc don't tells me much, and Python 3.8.2:”
@JulienPalard · 2020-02-26 · source
“So this is related to https://github.com/encode/httpx/issues/634, _backends/asyncio.py, SocketStream's close method: Obviously adding a await self.stream_writer.wait_closed() fixes #825 probably while reopening #634 So I'm going to…”
@JulienPalard · 2020-02-26 · source

Failure Signature (Search String)

  • $ ipython

Error Message

Stack trace
error.txt
Error Message ------------- $ ipython Python 3.8.0 (default, Dec 12 2019, 09:56:28) Type 'copyright', 'credits' or 'license' for more information IPython 7.12.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import httpx In [2]: async with httpx.AsyncClient() as client: ...: r = await client.get('https://www.example.org/') In [3]: r Out[3]: <Response [200 OK]> In [4]: Do you really want to exit ([y]/n)? An open stream object is being garbage collected; call "stream.close()" explicitly. source_traceback: Object created at (most recent call last): [...] File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", line 595, in run_until_complete self.run_forever() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", line 563, in run_forever self._run_once() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", line 183 ... (truncated) ...

Minimal Reproduction

repro.py
$ cat reproducer.py import asyncio import httpx async def mdk(): async with httpx.AsyncClient() as client: r = await client.get('https://www.example.org/') print(r.text) asyncio.run(mdk()) $ python reproducer.py TRACE [2020-02-26 09:37:29] httpx._config - load_ssl_context verify=True cert=None trust_env=True http2=False TRACE [2020-02-26 09:37:29] httpx._config - load_verify_locations cafile=/home/mdk/clones/python/docsbuild-scripts/.venv/lib/python3.8/site-packages/certifi/cacert.pem TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - acquire_connection origin=Origin(scheme='https' host='www.example.org' port=443) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - new_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.example.org' port=443)) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - start_connect tcp host='www.example.org' port=443 timeout=Timeout(timeout=5.0) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - connected http_version='HTTP/1.1' TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_headers method='GET' target='/' headers=Headers({'host': 'www.example.org', 'user-agent': 'python-httpx/0.11.1', 'accept': '*/*', 'accept-encoding': 'gzip, deflate', 'connection': 'keep-alive'}) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_data data=Data(<0 bytes>) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=NEED_DATA TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=Response(status_code=200, headers=[(b'content-encoding', b'gzip'), (b'accept-ranges', b'bytes'), (b'age', b'499617'), (b'cache-control', b'max-age=604800'), (b'content-type', b'text/html; charset=UTF-8'), (b'date', b'Wed, 26 Feb 2020 08:37:29 GMT'), (b'etag', b'"3147526947"'), (b'expires', b'Wed, 04 Mar 2020 08:37:29 GMT'), (b'last-modified', b'Thu, 17 Oct 2019 07:18:26 GMT'), (b'server', b'ECS (bsa/EB17)'), (b'vary', b'Accept-Encoding'), (b'x-cache', b'HIT'), (b'content-length', b'648')], http_version=b'1.1', reason=b'OK') DEBUG [2020-02-26 09:37:29] httpx._client - HTTP Request: GET https://www.example.org/ "HTTP/1.1 200 OK" TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=Data(<648 bytes>) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - receive_event event=EndOfMessage(headers=[]) TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - response_closed our_state=DONE their_state=DONE TRACE [2020-02-26 09:37:29] httpx._dispatch.connection_pool - release_connection connection=HTTPConnection(origin=Origin(scheme='https' host='www.example.org' port=443)) TRACE [2020-02-26 09:37:29] httpx._dispatch.connection - close_connection TRACE [2020-02-26 09:37:29] httpx._dispatch.http11 - send_event event=ConnectionClosed() <!doctype html> [... redacted (useless html) ...] /home/mdk/.local/lib/python3.8/asyncio/selector_events.py:694: ResourceWarning: unclosed transport <_SelectorSocketTransport fd=6> _warn(f"unclosed transport {self!r}", ResourceWarning, source=self) Object allocated at (most recent call last): File "reproducer.py", lineno 10 asyncio.run(mdk()) File "/home/mdk/.local/lib/python3.8/asyncio/runners.py", lineno 43 return loop.run_until_complete(main) File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 603 self.run_forever() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 570 self._run_once() File "/home/mdk/.local/lib/python3.8/asyncio/base_events.py", lineno 1859 handle._run() File "/home/mdk/.local/lib/python3.8/asyncio/ ... (truncated) ...

Environment

  • Python: 3.8.0
  • httpx: 0.11.1

What Broke

Users experience ResourceWarning due to unclosed transport leading to potential memory leaks.

Fix Options (Details)

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

Upgrade to version 0.7.2 or later.

When NOT to use: Do not use if it changes public behavior or if the failure cannot be reproduced.

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

Fix reference: https://github.com/encode/httpcore/pull/167

First fixed release: 0.7.2

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 if it changes public behavior or if the failure cannot be reproduced.

Verify Fix

verify
Re-run: ipython

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
0.7.2 Fixed

Related Issues

No related fixes found.

Sources

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