{"id":"477e3c8814937c2b","repo":"aio-libs/aiohttp","slug":"could-not-find-starting-boundary-self-boundary-r","errorCode":null,"errorMessage":"Could not find starting boundary {self._boundary!r}","messagePattern":"Could not find starting boundary (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/multipart.py","lineNumber":850,"sourceCode":"            )\n\n    def _get_boundary(self) -> str:\n        boundary = self._mimetype.parameters[\"boundary\"]\n        if len(boundary) > 70:\n            raise ValueError(\"boundary %r is too long (70 chars max)\" % boundary)\n\n        return boundary\n\n    async def _readline(self) -> bytes:\n        if self._unread:\n            return self._unread.pop()\n        return await self._content.readline()\n\n    async def _read_until_first_boundary(self) -> None:\n        while True:\n            chunk = await self._readline()\n            if chunk == b\"\":\n                raise ValueError(f\"Could not find starting boundary {self._boundary!r}\")\n            chunk = chunk.rstrip()\n            if chunk == self._boundary:\n                return\n            elif chunk == self._boundary + b\"--\":\n                self._at_eof = True\n                return\n\n    async def _read_boundary(self) -> None:\n        chunk = (await self._readline()).rstrip()\n        if chunk == self._boundary:\n            pass\n        elif chunk == self._boundary + b\"--\":\n            self._at_eof = True\n            epilogue = await self._readline()\n            next_line = await self._readline()\n\n            # the epilogue is expected and then either the end of input or the\n            # parent multipart boundary, if the parent boundary is found then","sourceCodeStart":832,"sourceCodeEnd":868,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/multipart.py#L832-L868","documentation":"Raised by MultipartReader._read_until_first_boundary() when the input stream returns empty (EOF) before the opening boundary line is found. This means the body is empty or the content does not contain the declared boundary at all, so the reader cannot locate the start of the first part.","triggerScenarios":"Calling `await reader.next()` (or iterating the reader) on a stream that ends before any line matching `--<boundary>` appears. Happens with empty bodies, truncated responses, or a boundary in Content-Type that does not match the actual body delimiters.","commonSituations":"Empty 200 response with a multipart Content-Type; proxy truncating the body; Content-Type boundary copied from a different message than the body; connection dropped mid-stream.","solutions":["Verify the Content-Type boundary matches the delimiters actually present in the body.","Check for an empty/truncated body upstream (Content-Length mismatch, early EOF).","Catch ValueError around the first next() call and treat as a transport/protocol error with retry or 502."],"exampleFix":"// before\npart = await reader.next()  # EOF before boundary -> ValueError\n// after\ntry:\n    part = await reader.next()\nexcept ValueError as e:\n    raise BadUpstream(f'multipart body missing boundary: {e}')\n","handlingStrategy":"try-catch","validationCode":"first = await reader._content.peek(2) if hasattr(reader._content, 'peek') else None\n# generally not feasible to validate without consuming; rely on try/except","typeGuard":null,"tryCatchPattern":"try:\n    part = await reader.next()\nexcept ValueError as e:\n    if 'Could not find starting boundary' in str(e):\n        raise BadUpstream('empty or truncated multipart body')\n    raise","preventionTips":["Verify Content-Length and that the body is non-empty before parsing.","Confirm the boundary in Content-Type matches the body delimiters.","Handle early-EOF from upstream connections gracefully."],"tags":["multipart","boundary","streaming","protocol"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}