encode/httpx · error · RuntimeError

Attempted to call a sync iterator on an async stream.

Error message

Attempted to call a sync iterator on an async stream.

What it means

Raised as `RuntimeError` by sync `iter_raw()` when `self.stream` is not a `SyncByteStream`. It means the Response is backed by an async stream (produced via `AsyncClient`) but you called a sync iteration method. httpx keeps sync and async transports strictly separate and will not bridge them implicitly.

Source

Thrown at httpx/_models.py:944

    def iter_lines(self) -> typing.Iterator[str]:
        decoder = LineDecoder()
        with request_context(request=self._request):
            for text in self.iter_text():
                for line in decoder.decode(text):
                    yield line
            for line in decoder.flush():
                yield line

    def iter_raw(self, chunk_size: int | None = None) -> typing.Iterator[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, SyncByteStream):
            raise RuntimeError("Attempted to call a sync iterator on an async stream.")

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

        with request_context(request=self._request):
            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

        self.close()

    def close(self) -> None:
        """

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Use the async equivalents: `await response.aread()`, `async for chunk in response.aiter_bytes():`.
  2. Keep sync and async response handling in separate, clearly-typed helper functions.
  3. If you must bridge, run the async client inside `asyncio.run(...)` and consume via async APIs.
  4. Type your helpers as accepting `httpx.Response` vs `httpx.AsyncClient`-produced responses distinctly.

Example fix

// before
async with httpx.AsyncClient() as c:
    r = await c.get(url)
for c in r.iter_bytes():  # RuntimeError: sync iterator on async stream
    ...

// after
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 AsyncByteStream, SyncByteStream

def is_async_response(resp: httpx.Response) -> bool:
    return isinstance(resp.stream, AsyncByteStream)

Type guard

import httpx
from httpx._transports.default import AsyncByteStream

def is_async_stream(resp: httpx.Response) -> bool:
    return isinstance(resp.stream, AsyncByteStream)

Try / catch

try:
    for chunk in response.iter_bytes():
        ...
except RuntimeError:
    # called sync method on async stream - switch to async
    async for chunk in response.aiter_bytes():
        ...

Prevention

When it happens

Trigger: Calling `response.iter_raw()` / `iter_bytes()` / `response.read()` (sync) on a Response obtained from `httpx.AsyncClient`.

Common situations: Mixing `httpx.Client` and `httpx.AsyncClient` in the same code path; converting sync code to async and forgetting to switch `.read()` to `.aread()` and `iter_bytes()` to `aiter_bytes()`; helper functions shared across sync/async callers.

Related errors


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