{"id":"f5694f28c7ed0fcd","repo":"aio-libs/aiohttp","slug":"separator-should-be-at-least-one-byte-string","errorCode":null,"errorMessage":"Separator should be at least one-byte string","messagePattern":"Separator should be at least one-byte string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/streams.py","lineNumber":386,"sourceCode":"            self._waiter = None\n\n    async def _fire_chunk_received(self, chunk: bytes) -> None:\n        cb = self._on_chunk_received\n        assert cb is not None\n        # Run under the same per-stream timer that _wait() uses, so a hung\n        # trace handler is bounded by sock_read just like a hung socket read would be.\n        with self._timer:\n            await cb(chunk)\n\n    async def readline(self, *, max_line_length: int | None = None) -> bytes:\n        return await self.readuntil(max_size=max_line_length)\n\n    async def readuntil(\n        self, separator: bytes = b\"\\n\", *, max_size: int | None = None\n    ) -> bytes:\n        seplen = len(separator)\n        if seplen == 0:\n            raise ValueError(\"Separator should be at least one-byte string\")\n\n        if self._exception is not None:\n            raise self._exception\n\n        chunk = b\"\"\n        chunk_size = 0\n        not_enough = True\n        max_size = max_size or self._high_water\n\n        while not_enough:\n            while self._buffer and not_enough:\n                offset = self._buffer_offset\n                ichar = self._buffer[0].find(separator, offset) + 1\n                # Read from current offset to found separator or to the end.\n                data = self._read_nowait_chunk(\n                    ichar - offset + seplen - 1 if ichar else -1\n                )\n                chunk += data","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/streams.py#L368-L404","documentation":"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''.","triggerScenarios":"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):]).","commonSituations":"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.","solutions":["Pass a non-empty bytes separator (default b'\\n' is usually what you want for line reads).","If you genuinely want everything, use await request.content.read(-1) instead of readuntil(b'').","Validate the separator before calling: `if not sep: raise ValueError(...)`."],"exampleFix":"// before\nline = await request.content.readuntil(sep)  # sep may be b''\n// after\nif not sep:\n    raise ValueError('separator must be non-empty')\nline = await request.content.readuntil(sep)","handlingStrategy":"validation","validationCode":"def safe_readuntil(stream, sep):\n    if not isinstance(sep, (bytes, bytearray)) or len(sep) == 0:\n        raise ValueError('separator must be non-empty bytes')\n    return stream.readuntil(bytes(sep))","typeGuard":null,"tryCatchPattern":"try:\n    line = await stream.readuntil(sep)\nexcept ValueError as e:\n    if 'Separator should be' in str(e):\n        raise ValueError('provide a non-empty separator') from e\n    raise","preventionTips":["Always validate separator length at the call site.","Default to b'\\n' unless you need a different delimiter.","Use read(-1) for un-delimited bulk reads."],"tags":["streams","validation","api"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}