{"id":"05fa0b24a44f2bf7","repo":"aio-libs/aiohttp","slug":"reading-after-eof","errorCode":null,"errorMessage":"Reading after EOF","messagePattern":"Reading after EOF","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/multipart.py","lineNumber":426,"sourceCode":"    async def _read_chunk_from_stream(self, size: int) -> bytes:\n        # Reads content chunk of body part with unknown length.\n        # The Content-Length header for body part is not necessary.\n        assert (\n            size >= self._boundary_len\n        ), \"Chunk size must be greater or equal than boundary length + 2\"\n        first_chunk = self._prev_chunk is None\n        if first_chunk:\n            # We need to re-add the CRLF that got removed from headers parsing.\n            self._prev_chunk = b\"\\r\\n\" + await self._content.read(size)\n\n        chunk = b\"\"\n        # content.read() may return less than size, so we need to loop to ensure\n        # we have enough data to detect the boundary.\n        while len(chunk) < self._boundary_len:\n            chunk += await self._content.read(size)\n            self._content_eof += int(self._content.at_eof())\n            if self._content_eof > 2:\n                raise ValueError(\"Reading after EOF\")\n            if self._content_eof:\n                break\n        if len(chunk) > size:\n            self._content.unread_data(chunk[size:])\n            chunk = chunk[:size]\n\n        assert self._prev_chunk is not None\n        window = self._prev_chunk + chunk\n        sub = b\"\\r\\n\" + self._boundary\n        if first_chunk:\n            idx = window.find(sub)\n        else:\n            idx = window.find(sub, max(0, len(self._prev_chunk) - len(sub)))\n        if idx >= 0:\n            # pushing boundary back to content\n            with warnings.catch_warnings():\n                warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n                self._content.unread_data(window[idx:])","sourceCodeStart":408,"sourceCodeEnd":444,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/multipart.py#L408-L444","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    async for part in reader:\n        data = await part.read()\nexcept ValueError as e:\n    if 'EOF' in str(e):\n        return web.Response(status=400, text='truncated multipart')","preventionTips":["Reject truncated multipart uploads at the edge.","Ensure read chunk size exceeds boundary length.","Validate the boundary appears before stream end."],"tags":["multipart","eof","truncated","boundary","parser"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}