aio-libs/aiohttp · warning · ValueError

start cannot be after end

Error message

start cannot be after end

What it means

Raised as ValueError by BaseRequest.http_range (aiohttp/web_request.py:628) when the parsed Range header specifies a start byte that is greater than or equal to the (adjusted) end byte. Because HTTP ranges are inclusive but Python slices are exclusive, aiohttp adds 1 to end before comparing (line 625); if start >= end after that adjustment, the range is nonsensical and aiohttp refuses it.

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 c0ef574e29)

Solutions

  1. Catch ValueError around request.http_range and respond with HTTPRequestedRangeNotSatisfiable (416) - which is the HTTP-standard response for an unsatisfiable range.
  2. Validate start <= end in client code before emitting Range.
  3. Let FileResponse handle Range natively instead of parsing it yourself - it already returns 416 for unsatisfiable ranges.

Example fix

// before
start, end = request.http_range.start, request.http_range.stop

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

Strategy: try-catch

Validate before calling

rng = request.headers.get("Range", "")
import re
m = re.match(r"^bytes=(\d+)-(\d+)$", rng)
if m and int(m.group(1)) > int(m.group(2)):
    raise web.HTTPRequestRangeNotSatisfiable()

Type guard

def range_start_le_end(value: str) -> bool:
    import re
    m = re.match(r"^bytes=(\d+)-(\d+)$", value)
    return not m or int(m.group(1)) <= int(m.group(2))

Try / catch

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

Prevention

When it happens

Trigger: A Range header like 'bytes=100-100' becomes start=100, end=101 (inclusive->exclusive) and is fine; but 'bytes=100-50' yields start=100, end=51 -> start >= end -> ValueError. Also triggered by 'bytes=5-5' edge cases only when the math produces equality after the +1 step is bypassed.

Common situations: Client bugs computing Range from off-by-one indices; manual Range headers in tests; a download manager that retries a partial range with stale end values.

Related errors


AI-assisted analysis of aio-libs/aiohttp@c0ef574e29 (2026-08-04). Data as JSON: /data/errors/429ff529e82b1179.json. Report an issue: GitHub.