aio-libs/aiohttp · error · ValueError

start cannot be after end

Error message

start cannot be after end

What it means

After converting the inclusive Range end to an exclusive bound (end += 1), http_range checks start >= end and raises ValueError('start cannot be after end'). E.g. Range: bytes=100-100 yields start=100,end=101 (valid), but bytes=100-50 fails because start (100) >= end (51).

Solutions

  1. Catch ValueError around request.http_range and respond 416.
  2. Serve the full content when the range is logically invalid.
  3. Validate start < end client-side before sending the Range header.

Example fix

try:
    rng = request.http_range
except ValueError:
    raise web.HTTPRequestRangeNotSatisfiable()
Defensive patterns

Strategy: try-catch

Validate before calling

start, end = parse_range(request.headers.get('Range'))
if start is not None and end is not None and start >= end:
    raise web.HTTPRequestRangeNotSatisfiable()

Try / catch

try:
    rng = request.http_range
except ValueError:
    raise web.HTTPRequestRangeNotSatisfiable()

Prevention

When it happens

Trigger: Range: bytes=100-50, Range: bytes=10-9 — client supplies a start beyond the end.

Common situations: Off-by-one errors in custom download clients; buggy seek logic computing ranges; proxied requests from a tool that miscalculates suffix ranges.

Related errors


AI-assisted analysis of aio-libs/aiohttp@d041d4d0fd (2026-08-11). Data as JSON: /api/errors/429ff529e82b1179. Report an issue: GitHub.

Appendix: source

Thrown at aiohttp/web_request.py:628

                pattern = r"^bytes=(\d*)-(\d*)$"
                start, end = re.findall(pattern, rng, re.ASCII)[0]
            except IndexError:  # pattern was not found in header
                raise ValueError("range not in acceptable format")

            end = int(end) if end else None
            start = int(start) if start else None

            if start is None and end is not None:
                # end with no start is to return tail of content
                start = -end
                end = None

            if start is not None and end is not None:
                # end is inclusive in range header, exclusive for slice
                end += 1

                if start >= end:
                    raise ValueError("start cannot be after end")

            if start is end is None:  # No valid range supplied
                raise ValueError("No start or end of range specified")

        return slice(start, end, 1)

    @reify
    def content(self) -> StreamReader:
        """Return raw payload stream."""
        return self._payload

    @property
    def can_read_body(self) -> bool:
        """Return True if request's HTTP BODY can be read, False otherwise."""
        return not self._payload.at_eof()

    @reify
    def body_exists(self) -> bool:

View on GitHub (pinned to d041d4d0fd)