aio-libs/aiohttp · error · ValueError
Separator should be at least one-byte string
Error message
Separator should be at least one-byte string
What it means
Raised by StreamReader.readuntil when the separator argument is an empty byte string (length 0). An empty separator would match at every position and never terminate the read, so aiohttp requires at least one byte. The default separator is b'\n'.
Solutions
- Provide a non-empty separator, e.g. await stream.readuntil(b'\r\n').
- Validate the separator before calling: if not separator: raise ValueError(...).
- Use the default readline() (newline-delimited) when you just want line-by-line reading.
Example fix
# before
sep = boundary.encode() # boundary was ''
line = await stream.readuntil(sep)
# after
if not boundary:
raise ValueError('boundary must not be empty')
line = await stream.readuntil(boundary.encode()) Defensive patterns
Strategy: validation
Validate before calling
if not separator:
raise ValueError('separator must be non-empty')
line = await stream.readuntil(separator) Type guard
def valid_separator(sep) -> bool:
return isinstance(sep, (bytes, bytearray)) and len(sep) >= 1 Try / catch
try:
line = await stream.readuntil(separator)
except ValueError as e:
if 'Separator' in str(e):
line = await stream.readuntil(b'\n')
else:
raise Prevention
- Default to b'\n' for line reads.
- Validate delimiters sourced from config/headers before use.
- Unit-test parsing code with empty-boundary inputs.
When it happens
Trigger: Calling await stream.readuntil(b'') explicitly; passing a separator computed from user input or a header that resolved to empty bytes; calling readline()/readuntil() with a variable that happened to be b''.
Common situations: Parsing a delimiter from configuration where the delimiter was unset; splitting on a boundary string that turned out empty; passing None and converting with bytes(None-ish).
Related errors
- Got more than bytes when reading: .
- 1007
- A ":" is not allowed in login (RFC 7617#section-2)
- A ":" is not allowed in username (RFC 1945#section-11.1)
- bad content disposition parameter
AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11).
Data as JSON: /api/errors/f5694f28c7ed0fcd.
Report an issue: GitHub.
Appendix: source
Thrown at aiohttp/streams.py:386
self._waiter = None
async def _fire_chunk_received(self, chunk: bytes) -> None:
cb = self._on_chunk_received
assert cb is not None
# Run under the same per-stream timer that _wait() uses, so a hung
# trace handler is bounded by sock_read just like a hung socket read would be.
with self._timer:
await cb(chunk)
async def readline(self, *, max_line_length: int | None = None) -> bytes:
return await self.readuntil(max_size=max_line_length)
async def readuntil(
self, separator: bytes = b"\n", *, max_size: int | None = None
) -> bytes:
seplen = len(separator)
if seplen == 0:
raise ValueError("Separator should be at least one-byte string")
if self._exception is not None:
raise self._exception
chunk = b""
chunk_size = 0
not_enough = True
max_size = max_size or self._high_water
while not_enough:
while self._buffer and not_enough:
offset = self._buffer_offset
ichar = self._buffer[0].find(separator, offset) + 1
# Read from current offset to found separator or to the end.
data = self._read_nowait_chunk(
ichar - offset + seplen - 1 if ichar else -1
)
chunk += dataView on GitHub (pinned to d041d4d0fd)