aio-libs/aiohttp · error · ValueError
Reading after EOF
Error message
Reading after EOF
What it means
ValueError('Reading after EOF') raised in BodyPartReader._read_chunk_from_stream when the underlying content stream reports at_eof() more than twice while the reader is still trying to accumulate enough bytes (>= boundary length) to detect the boundary. The part cannot be framed because the stream ended prematurely.
Source
Thrown at aiohttp/multipart.py:426
async def _read_chunk_from_stream(self, size: int) -> bytes:
# Reads content chunk of body part with unknown length.
# The Content-Length header for body part is not necessary.
assert (
size >= self._boundary_len
), "Chunk size must be greater or equal than boundary length + 2"
first_chunk = self._prev_chunk is None
if first_chunk:
# We need to re-add the CRLF that got removed from headers parsing.
self._prev_chunk = b"\r\n" + await self._content.read(size)
chunk = b""
# content.read() may return less than size, so we need to loop to ensure
# we have enough data to detect the boundary.
while len(chunk) < self._boundary_len:
chunk += await self._content.read(size)
self._content_eof += int(self._content.at_eof())
if self._content_eof > 2:
raise ValueError("Reading after EOF")
if self._content_eof:
break
if len(chunk) > size:
self._content.unread_data(chunk[size:])
chunk = chunk[:size]
assert self._prev_chunk is not None
window = self._prev_chunk + chunk
sub = b"\r\n" + self._boundary
if first_chunk:
idx = window.find(sub)
else:
idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub)))
if idx >= 0:
# pushing boundary back to content
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
self._content.unread_data(window[idx:])View on GitHub (pinned to c0ef574e29)
Solutions
- Confirm the full multipart body is delivered (no truncation).
- Ensure the boundary actually appears in the stream.
- Pass a read chunk size >= boundary length + 2 to BodyPartReader.
- Catch ValueError and return 400 / retry the upload.
Defensive patterns
Strategy: try-catch
Try / catch
try:
async for part in reader:
data = await part.read()
except ValueError as e:
if 'EOF' in str(e):
return web.Response(status=400, text='truncated multipart') Prevention
- Reject truncated multipart uploads at the edge.
- Ensure read chunk size exceeds boundary length.
- Validate the boundary appears before stream end.
When it happens
Trigger: While reading a part of unknown length from the stream, each read that yields too few bytes to locate the boundary increments _content_eof; after the third occurrence (content_eof > 2) the reader raises ValueError because it is being asked to read past EOF.
Common situations: Truncated multipart upload (connection dropped mid-body); boundary not actually present in the stream so the reader runs to EOF; a stream whose read() returns empty before EOF is signalled; very large boundary vs small chunk size.
Related errors
- Reader did not read all the data or it is malformed
- Not enough data to satisfy content length header (received {
- Not enough data to satisfy transfer length header.
- invalid Content-Length: {length!r}
- boundary missed for Content-Type: %s
AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04).
Data as JSON: /data/errors/05fa0b24a44f2bf7.json.
Report an issue: GitHub.