{"record":{"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":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/multipart.py","lineNumber":854,"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":836,"sourceCodeEnd":872,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/multipart.py#L836-L872","documentation":"While scanning for the opening boundary in _read_until_first_boundary, each readline() must eventually yield a non-empty line; an empty chunk means the stream ended before the boundary appeared. The reader then raises ValueError naming the boundary it expected.","triggerScenarios":"An empty multipart body, a body that contains only an epilogue, a body whose first line is the closing boundary ('--BOUNDARY--') with no opening, or a truncated stream with no boundary at all.","commonSituations":"Empty POST bodies, race conditions where the body is read after the connection closed, producers that forget the opening boundary, proxies that drop the preamble.","solutions":["Ensure the producer writes the opening boundary ('--BOUNDARY\\r\\n') as the first non-preamble line.","Reject empty bodies early when a multipart Content-Type is declared.","Verify the connection is not closed before the body is fully read; check Content-Length on the outer request.","Catch ValueError and return 400 with a clear message about missing starting boundary."],"exampleFix":"// before\n(body is empty or has no boundary line)\n\n// after\n------WebKitFormBoundary\\r\\nContent-Disposition: form-data; name=\"f\"\\r\\n\\r\\nv\\r\\n------WebKitFormBoundary--\\r\\n","handlingStrategy":"try-catch","validationCode":"# reject obviously empty bodies before parsing\nif request.can_read_body and request.content_length in (0, None):\n    # likely empty multipart body; refuse early if a body was expected\n    if request.headers.get(CONTENT_TYPE, '').startswith('multipart/'):\n        return web.Response(status=400, text='Empty multipart body')","typeGuard":null,"tryCatchPattern":"try:\n    async for part in reader:\n        process(part)\nexcept ValueError as e:\n    if 'starting boundary' in str(e):\n        return web.Response(status=400, text='Missing multipart starting boundary')\n    raise","preventionTips":["Ensure producers emit the opening boundary as the first non-preamble line.","Reject empty bodies when Content-Type declares multipart.","Verify the connection is not prematurely closed before reading the body."],"tags":["multipart","boundary","framing","stream-integrity","empty-body"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}