aio-libs/aiohttp · error · ValueError
Could not find starting boundary
Error message
Could not find starting boundary {self._boundary!r} What it means
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.
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.
Example fix
// before (body is empty or has no boundary line) // after ------WebKitFormBoundary\r\nContent-Disposition: form-data; name="f"\r\n\r\nv\r\n------WebKitFormBoundary--\r\n
Defensive patterns
Strategy: try-catch
Validate before calling
# reject obviously empty bodies before parsing
if request.can_read_body and request.content_length in (0, None):
# likely empty multipart body; refuse early if a body was expected
if request.headers.get(CONTENT_TYPE, '').startswith('multipart/'):
return web.Response(status=400, text='Empty multipart body') Try / catch
try:
async for part in reader:
process(part)
except ValueError as e:
if 'starting boundary' in str(e):
return web.Response(status=400, text='Missing multipart starting boundary')
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid boundary , expected
- Reader did not read all the data or it is malformed
- Reading after EOF
- boundary missed for Content-Type
- boundary %r is too long (70 chars max)
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/477e3c8814937c2b.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/multipart.py:854
)
def _get_boundary(self) -> str:
boundary = self._mimetype.parameters["boundary"]
if len(boundary) > 70:
raise ValueError("boundary %r is too long (70 chars max)" % boundary)
return boundary
async def _readline(self) -> bytes:
if self._unread:
return self._unread.pop()
return await self._content.readline()
async def _read_until_first_boundary(self) -> None:
while True:
chunk = await self._readline()
if chunk == b"":
raise ValueError(f"Could not find starting boundary {self._boundary!r}")
chunk = chunk.rstrip()
if chunk == self._boundary:
return
elif chunk == self._boundary + b"--":
self._at_eof = True
return
async def _read_boundary(self) -> None:
chunk = (await self._readline()).rstrip()
if chunk == self._boundary:
pass
elif chunk == self._boundary + b"--":
self._at_eof = True
epilogue = await self._readline()
next_line = await self._readline()
# the epilogue is expected and then either the end of input or the
# parent multipart boundary, if the parent boundary is found thenView on GitHub (pinned to d041d4d0fd)