encode/httpx · error · StreamConsumed
Attempted to read or stream some content, but the content ha
Error message
Attempted to read or stream some content, but the content has already been streamed. For requests, this could be due to passing a generator as request content, and then receiving a redirect response or a secondary request as part of an authentication flow.For responses, this could be due to attempting to stream the response content more than once.
What it means
Raised as `StreamConsumed` by the sync `iter_raw()` method when `is_stream_consumed` is already True. A streaming body can be read exactly once; a second attempt is rejected because the underlying network bytes are gone. The message notes both request-side (generator content + redirect/auth retry) and response-side (double-streaming) causes.
Source
Thrown at httpx/_models.py:940
yield chunk # pragma: no cover
for chunk in chunker.flush():
yield chunk
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
View on GitHub (pinned to b5addb64f0)
Solutions
- Buffer the body once with `response.read()` and reuse `response.content` for subsequent access.
- For request bodies that may be replayed (redirects/auth), pass bytes or a file object with `seek()`, not a generator.
- Tee the stream into an in-memory buffer if you genuinely need multiple passes.
- Re-issue the request if you need a fresh stream.
Example fix
// before
for chunk in response.iter_bytes():
...
for chunk in response.iter_bytes(): # StreamConsumed
...
// after
response.read()
body = response.content # reuse as often as needed Defensive patterns
Strategy: validation
Validate before calling
def can_iter(resp: httpx.Response) -> bool:
return not resp.is_stream_consumed and not resp.is_closed Type guard
import httpx
def stream_is_fresh(resp: httpx.Response) -> bool:
return not (resp.is_stream_consumed or resp.is_closed) Try / catch
try:
for chunk in response.iter_bytes():
...
except httpx.StreamConsumed:
response.read() # fall back to buffered content if already consumed once
body = response.content Prevention
- Buffer once with .read() and reuse .content for multiple consumers.
- Never pass a generator as request content if redirects/auth may replay it.
- Tee into memory only when truly necessary.
When it happens
Trigger: Iterating `response.iter_raw()` / `iter_bytes()` a second time; or on the request side, passing a generator as `content=` and letting httpx replay it after a redirect or during an auth challenge (the generator was already exhausted).
Common situations: Caching/memoizing a streaming response and then trying to iterate again; logging the body via `iter_bytes()` and then handing the same response to JSON parsing; using `yield`-based request bodies with redirects.
Related errors
- Attempted to access streaming response content, without havi
- Attempted to read or stream content, but the stream has been
- The request instance has not been set on this response.
- Cannot call `raise_for_status` as the request instance has n
- Attempted to call a sync iterator on an async stream.
AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04).
Data as JSON: /data/errors/266a188feb523d88.json.
Report an issue: GitHub.