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 has length 0. readuntil scans the buffer for the separator, so an empty separator is meaningless (every position matches). aiohttp validates len(separator) == 0 and raises ValueError rather than returning the entire buffer. The default separator is b'\n' so this only fires when the caller explicitly passes b''.

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 += data

View on GitHub (pinned to c0ef574e29)

Solutions

  1. Pass a non-empty bytes separator (default b'\n' is usually what you want for line reads).
  2. If you genuinely want everything, use await request.content.read(-1) instead of readuntil(b'').
  3. Validate the separator before calling: `if not sep: raise ValueError(...)`.

Example fix

// before
line = await request.content.readuntil(sep)  # sep may be b''
// after
if not sep:
    raise ValueError('separator must be non-empty')
line = await request.content.readuntil(sep)
Defensive patterns

Strategy: validation

Validate before calling

def safe_readuntil(stream, sep):
    if not isinstance(sep, (bytes, bytearray)) or len(sep) == 0:
        raise ValueError('separator must be non-empty bytes')
    return stream.readuntil(bytes(sep))

Try / catch

try:
    line = await stream.readuntil(sep)
except ValueError as e:
    if 'Separator should be' in str(e):
        raise ValueError('provide a non-empty separator') from e
    raise

Prevention

When it happens

Trigger: Calling await request.content.readuntil(b'') or request.content.readline(max_line_length=...) after monkey-patching the separator to empty; passing a computed separator that resolves to b'' due to a bug (e.g. separator = prefix[len(prefix):]).

Common situations: Programmatic separator derived from user input that can be empty; copy-paste from readuntil docs where the default was overridden; trying to use readuntil as a non-delimited bulk read.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/f5694f28c7ed0fcd.json. Report an issue: GitHub.