{"record":{"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/d041d4d0fd48c3f0832084d33be16cf1c4835f85/aiohttp/streams.py#L368-L404","documentation":"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'.","triggerScenarios":"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''.","commonSituations":"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).","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."],"exampleFix":"# before\nsep = boundary.encode()  # boundary was ''\nline = await stream.readuntil(sep)\n\n# after\nif not boundary:\n    raise ValueError('boundary must not be empty')\nline = await stream.readuntil(boundary.encode())","handlingStrategy":"validation","validationCode":"if not separator:\n    raise ValueError('separator must be non-empty')\nline = await stream.readuntil(separator)","typeGuard":"def valid_separator(sep) -> bool:\n    return isinstance(sep, (bytes, bytearray)) and len(sep) >= 1","tryCatchPattern":"try:\n    line = await stream.readuntil(separator)\nexcept ValueError as e:\n    if 'Separator' in str(e):\n        line = await stream.readuntil(b'\\n')\n    else:\n        raise","preventionTips":["Default to b'\\n' for line reads.","Validate delimiters sourced from config/headers before use.","Unit-test parsing code with empty-boundary inputs."],"tags":["streams","validation","parsing"],"backgroundTag":null,"analyzedSha":"d041d4d0fd48c3f0832084d33be16cf1c4835f85","analyzedAt":"2026-08-11T20:44:15.550Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}