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%.
@@ -1,9 +1,17 @@
import mimetypes
import os
+import re
import typing
from io import BytesIO
$ 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) ...
Re-run: ipython
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).
- 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.”
“Raised https://bugs.python.org/issue39758”
“Tried with HTTPX_LOG_LEVE=trace because tracemalloc don't tells me much, and Python 3.8.2:”
“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…”
Failure Signature (Search String)
- $ ipython
Error Message
Stack trace
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
$ 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.
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.
When NOT to Use This Fix
- Do not use if it changes public behavior or if the failure cannot be reproduced.
Verify Fix
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
| Version | Status |
|---|---|
| 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.