python/cpython · error · ValueError

Separator should be at least one-byte string

Error message

Separator should be at least one-byte string

What it means

ValueError raised by StreamReader.readuntil() when the shortest separator is a zero-length bytes object (min_seplen == 0). A zero-length separator would match everywhere and make the consume-buffer arithmetic (which reserves max_seplen - 1 bytes) degenerate, so it is rejected upfront.

Source

Thrown at Lib/asyncio/streams.py:613

        will be left in the internal buffer, so it can be read again.

        The ``separator`` may also be a tuple of separators. In this
        case the return value will be the shortest possible that has any
        separator as the suffix. For the purposes of LimitOverrunError,
        the shortest possible separator is considered to be the one that
        matched.
        """
        if isinstance(separator, tuple):
            # Makes sure shortest matches wins
            separator = sorted(separator, key=len)
        else:
            separator = [separator]
        if not separator:
            raise ValueError('Separator should contain at least one element')
        min_seplen = len(separator[0])
        max_seplen = len(separator[-1])
        if min_seplen == 0:
            raise ValueError('Separator should be at least one-byte string')

        if self._exception is not None:
            raise self._exception

        # Consume whole buffer except last bytes, which length is
        # one less than max_seplen. Let's check corner cases with
        # separator[-1]='SEPARATOR':
        # * we have received almost complete separator (without last
        #   byte). i.e buffer='some textSEPARATO'. In this case we
        #   can safely consume max_seplen - 1 bytes.
        # * last byte of buffer is first byte of separator, i.e.
        #   buffer='abcdefghijklmnopqrS'. We may safely consume
        #   everything except that last byte, but this require to
        #   analyze bytes of buffer that match partial separator.
        #   This is slow and/or require FSM. For this case our
        #   implementation is not optimal, since require rescanning
        #   of data that is known to not belong to separator. In
        #   real world, separator will not be so long to notice

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Filter empty separators out of the collection before calling readuntil
  2. Validate delimiter strings at the API/config boundary: must be non-empty bytes
  3. If you intended an any-position single-byte match, iterate bytes manually or use read() and buffer splitting yourself

Example fix

// before
seps = tuple(d.encode() for d in delims)  # may contain b''
line = await reader.readuntil(seps)

// after
seps = tuple(d.encode() for d in delims if d)
if not seps:
    raise ValueError('at least one non-empty delimiter required')
line = await reader.readuntil(seps)
Defensive patterns

Strategy: validation

Validate before calling

def checked_separator(sep: bytes) -> bytes:
    if not isinstance(sep, (bytes, bytearray)) or len(sep) == 0:
        raise ValueError(f'separator must be non-empty bytes, got {sep!r}')
    return bytes(sep)

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: await reader.readuntil(b''), or readuntil((b'', b'\n')) where sorting by length puts the empty separator first; also a tuple containing an empty element after dynamic construction.

Common situations: Splitting user-supplied input on a delimiter where the user passed an empty string; separator lists assembled from parsed headers (e.g. an empty value for one terminator); copy-paste of str.split-style habits where '' is tolerated.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/fde9faf9f2e3e4a8. Report an issue: GitHub.