aio-libs/aiohttp · error · RuntimeError
Called end_chunk_receiving without calling begin_chunk_recei
Error message
Called end_chunk_receiving without calling begin_chunk_receiving first
What it means
Internal guard in StreamReader.end_http_chunk_receiving: it requires _http_chunk_splits to be non-None, i.e. begin_http_chunk_receiving was called first. Calling end without begin indicates a parser-state mismatch — the parser emitted a chunk-end marker with no matching begin. Like 185, this is an aiohttp-internal invariant rather than something typical application code triggers.
Source
Thrown at aiohttp/streams.py:315
if waiter is not None:
self._waiter = None
set_result(waiter, None)
if self._size > self._high_water:
self._protocol.pause_reading()
return False
def begin_http_chunk_receiving(self) -> None:
if self._http_chunk_splits is None:
if self.total_bytes:
raise RuntimeError(
"Called begin_http_chunk_receiving when some data was already fed"
)
self._http_chunk_splits = collections.deque()
def end_http_chunk_receiving(self) -> None:
if self._http_chunk_splits is None:
raise RuntimeError(
"Called end_chunk_receiving without calling "
"begin_chunk_receiving first"
)
# self._http_chunk_splits contains logical byte offsets from start of
# the body transfer. Each offset is the offset of the end of a chunk.
# "Logical" means bytes, accessible for a user.
# If no chunks containing logical data were received, current position
# is difinitely zero.
pos = self._http_chunk_splits[-1] if self._http_chunk_splits else 0
if self.total_bytes == pos:
# We should not add empty chunks here. So we check for that.
# Note, when chunked + gzip is used, we can receive a chunk
# of compressed data, but that data may not be enough for gzip FSM
# to yield any uncompressed data. That's why current position may
# not change after receiving a chunk.
returnView on GitHub (pinned to c0ef574e29)
Solutions
- Inspect raw upstream bytes to confirm chunk framing is well-formed (size CRLF data CRLF ...).
- Avoid calling the chunk API manually — delegate to the HTTP parser.
- Upgrade aiohttp/llhttp; if it reproduces, file an issue with full response capture.
- For custom StreamReader feeding, always bracket with begin/end and never call end twice.
Defensive patterns
Strategy: try-catch
Try / catch
try:
body = await resp.read()
except RuntimeError as e:
if 'end_chunk_receiving' in str(e):
log.warning('upstream chunk framing mismatch on %s', url)
raise BadGateway('malformed chunked response') from e
raise Prevention
- Never pair begin_http_chunk_receiving/end_http_chunk_receiving incorrectly in custom protocols.
- Let the HTTP parser own chunk framing.
- Capture raw bytes when reproducing for upstream bug reports.
When it happens
Trigger: The HTTP parser (http_parser.py:1045) calls payload.end_http_chunk_receiving() while _http_chunk_splits is None — e.g. malformed chunked body missing the chunk-size line, or a custom protocol object that calls end_http_chunk_receiving without first calling begin_http_chunk_receiving.
Common situations: Malformed upstream chunked body (server bug); a man-in-the-middle truncating chunk framing; a custom request/response feeding path that mishandles the chunk API; race between parser version and streams internals.
Related errors
- Called begin_http_chunk_receiving when some data was already
- EmptyStreamReader._on_chunk_received is read-only
- Invalid HTTP header: {hdr!r}
- Not enough data to satisfy transfer length header.
- Too many trailers received
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/da8d8596a7f8a0a0.json.
Report an issue: GitHub.