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

  1. Confirm the full multipart body is delivered (no truncation).
  2. Ensure the boundary actually appears in the stream.
  3. Pass a read chunk size >= boundary length + 2 to BodyPartReader.
  4. 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

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


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/05fa0b24a44f2bf7.json. Report an issue: GitHub.