encode/httpx · error · RuntimeError

Attempted to call an async iterator on a sync stream.

Error message

Attempted to call an async iterator on a sync stream.

What it means

Raised as `RuntimeError` by async `aiter_raw()` when `self.stream` is not an `AsyncByteStream`. The Response came from a sync `httpx.Client` but you used an async iteration method. httpx refuses to silently run a sync iterator inside an async context.

Source

Thrown at httpx/_models.py:1048

        with request_context(request=self._request):
            async for text in self.aiter_text():
                for line in decoder.decode(text):
                    yield line
            for line in decoder.flush():
                yield line

    async def aiter_raw(
        self, chunk_size: int | None = None
    ) -> typing.AsyncIterator[bytes]:
        """
        A byte-iterator over the raw response content.
        """
        if self.is_stream_consumed:
            raise StreamConsumed()
        if self.is_closed:
            raise StreamClosed()
        if not isinstance(self.stream, AsyncByteStream):
            raise RuntimeError("Attempted to call an async iterator on a sync stream.")

        self.is_stream_consumed = True
        self._num_bytes_downloaded = 0
        chunker = ByteChunker(chunk_size=chunk_size)

        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:
        """

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Use sync iteration (`for chunk in response.iter_bytes()`, `response.read()`) for sync-client responses.
  2. Pick one concurrency model per code path and stick to it.
  3. If async is required, switch the client to `httpx.AsyncClient`.
  4. Type-annotate helpers so mypy/pyright flags the mismatch.

Example fix

// before
async def handler():
    r = sync_client.get(url)
    async for c in r.aiter_bytes():  # RuntimeError
        ...

// after
async def handler():
    async with httpx.AsyncClient() as c:
        r = await c.get(url)
        async for c in r.aiter_bytes():
            ...
Defensive patterns

Strategy: type-guard

Validate before calling

from httpx._transports.default import SyncByteStream

def is_sync_response(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:
    async for chunk in response.aiter_bytes():
        ...
except RuntimeError:
    # sync stream - use sync iteration
    for chunk in response.iter_bytes():
        ...

Prevention

When it happens

Trigger: Calling `async for chunk in response.aiter_bytes():` or `await response.aread()` on a Response returned by sync `httpx.Client.get()`.

Common situations: Accidentally wrapping sync client calls in an async function; shared helpers originally written for async that get reused with a sync client; framework code that assumes async responses everywhere.

Related errors


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