encode/httpx · error · RuntimeError

Attempted to call an async close on a sync stream.

Error message

Attempted to call an async close on a sync stream.

What it means

Raised as `RuntimeError` by async `aclose()` when `self.stream` is not an `AsyncByteStream`. You awaited the async close on a Response whose underlying stream is sync (came from `httpx.Client`).

Source

Thrown at httpx/_models.py:1071

        with request_context(request=self._request):
            async for raw_stream_bytes in self.stream:
                self._num_bytes_downloaded += len(raw_stream_bytes)
                for chunk in chunker.decode(raw_stream_bytes):
                    yield chunk

        for chunk in chunker.flush():
            yield chunk

        await self.aclose()

    async def aclose(self) -> None:
        """
        Close the response and release the connection.
        Automatically called if the response body is read to completion.
        """
        if not isinstance(self.stream, AsyncByteStream):
            raise RuntimeError("Attempted to call an async close on a sync stream.")

        if not self.is_closed:
            self.is_closed = True
            with request_context(request=self._request):
                await self.stream.aclose()


class Cookies(typing.MutableMapping[str, str]):
    """
    HTTP Cookies, as a mutable mapping.
    """

    def __init__(self, cookies: CookieTypes | None = None) -> None:
        if cookies is None or isinstance(cookies, dict):
            self.jar = CookieJar()
            if isinstance(cookies, dict):
                for key, value in cookies.items():
                    self.set(key, value)

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Use `response.close()` for sync-client responses.
  2. Keep cleanup logic paired with the matching client type.
  3. Switch to `httpx.AsyncClient` if the surrounding code is async.
  4. Branch the cleanup: `if isinstance(resp.stream, httpx._transports.default.AsyncByteStream): await resp.aclose() else: resp.close()`.

Example fix

// before
r = sync_client.get(url)
await r.aclose()  # RuntimeError

// after
r = sync_client.get(url)
r.close()
Defensive patterns

Strategy: type-guard

Validate before calling

from httpx._transports.default import SyncByteStream

def needs_close(resp: httpx.Response) -> bool:
    return isinstance(resp.stream, SyncByteStream)

Type guard

import httpx
from httpx._transports.default import SyncByteStream

def is_sync_stream(resp: httpx.Response) -> bool:
    return isinstance(resp.stream, SyncByteStream)

Try / catch

try:
    await response.aclose()
except RuntimeError:
    response.close()

Prevention

When it happens

Trigger: Calling `await response.aclose()` on a Response produced by sync `httpx.Client`.

Common situations: Generic async cleanup helpers that always `await resp.aclose()`; mixing a sync client into an async service; partial migration leaving sync clients behind.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/7f613e413ffa741d.json. Report an issue: GitHub.