{"record":{"id":"eb4bbd562da429d3","repo":"commaai/openpilot","slug":"missing-multipart-boundary-self-url","errorCode":null,"errorMessage":"Missing multipart boundary ({self._url})","messagePattern":"Missing multipart boundary \\((.+?)\\)","errorType":"exception","errorClass":"URLFileException","httpStatus":null,"severity":"error","filePath":"openpilot/tools/lib/url_file.py","lineNumber":181,"sourceCode":"    self._pos += len(data[0])\n    return data[0]\n\n  def get_multi_range(self, ranges: list[tuple[int, int]]) -> list[bytes]:\n    # HTTP range requests are inclusive\n    assert all(e > s for s, e in ranges), \"Range end must be greater than start\"\n    rs = [f\"{s}-{e-1}\" for s, e in ranges if e > s]\n\n    r = self._request(\"GET\", self._url, headers={\"Range\": \"bytes=\" + \",\".join(rs)})\n    if r.status not in [200, 206]:\n      raise URLFileException(f\"Expected 206 or 200 response {r.status} ({self._url})\")\n\n    ctype = (r.headers.get(\"content-type\") or \"\").lower()\n    if \"multipart/byteranges\" not in ctype:\n      return [r.data,]\n\n    m = re.search(r'boundary=\"?([^\";]+)\"?', ctype)\n    if not m:\n      raise URLFileException(f\"Missing multipart boundary ({self._url})\")\n    boundary = m.group(1).encode()\n\n    parts = []\n    for chunk in r.data.split(b\"--\" + boundary):\n      if b\"\\r\\n\\r\\n\" not in chunk:\n        continue\n      payload = chunk.split(b\"\\r\\n\\r\\n\", 1)[1].rstrip(b\"\\r\\n\")\n      if payload and payload != b\"--\":\n        parts.append(payload)\n    if len(parts) != len(ranges):\n      raise URLFileException(f\"Expected {len(ranges)} parts, got {len(parts)} ({self._url})\")\n    return parts\n\n  def seekable(self) -> bool:\n    return True\n\n  def seek(self, pos: int, whence: int = 0) -> int:\n    pos = int(pos)","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/commaai/openpilot/blob/516ec1e68203439a73f340f1d0b3b91eabc626ee/openpilot/tools/lib/url_file.py#L163-L199","documentation":"When a multi-range response's Content-Type is multipart/byteranges, url_file must split parts by the boundary parameter in that header. If 'boundary=' is missing (or unparseable by its regex), it cannot split and raises. Malformed server/proxy responses cause this; compliant servers always include it.","triggerScenarios":"get_multi_range() with multiple ranges against a server/proxy that sends multipart/byteranges but omits the boundary parameter, or formats it unusually (e.g. boundary without '=' in a form the regex misses).","commonSituations":"Intermediate proxies (corporate proxies, some CDNs) rewriting responses; custom or self-hosted file servers with sloppy header generation.","solutions":["Issue single ranges instead (one range per request) so the response is never multipart: [uf.get_multi_range([(s, e)])[0] for s, e in ranges]","Inspect the raw header with curl -v and confirm what the server actually sends","If you control the server, always emit 'Content-Type: multipart/byteranges; boundary=...'"],"exampleFix":"# before\nparts = uf.get_multi_range(ranges)\n\n# after\nparts = [uf.get_multi_range([r])[0] for r in ranges]  # one range per request: no multipart parsing","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    parts = uf.get_multi_range(ranges)\nexcept URLFileException as e:\n    if 'multipart boundary' in str(e):\n        parts = [uf.get_multi_range([r])[0] for r in ranges]  # single-range requests","preventionTips":["Issue one range per request for maximum server compatibility","Avoid multi-range reads through proxies you don't control","Log the raw content-type header when debugging range behavior"],"tags":["network","http","multipart","url-file"],"backgroundTag":null,"analyzedSha":"516ec1e68203439a73f340f1d0b3b91eabc626ee","analyzedAt":"2026-08-15T00:17:37.461Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}