harry0703/MoneyPrinterTurbo · error · HttpException

{request_id}: requested range is not satisfiable

Error message

{request_id}: requested range is not satisfiable

What it means

Raised by _parse_byte_range in app/controllers/v1/video.py (first branch) when the file being streamed has size <= 0. Even without a Range header the function refuses, because there is no satisfiable byte range for an empty (or zero-length) file and video playback would be undefined. Maps to HTTP 416.

Source

Thrown at app/controllers/v1/video.py:130

        logger.warning(
            f"skip unsafe task output path, request_id: {request_id}, path: {file}, "
            f"error: {str(exc)}"
        )
        return file

    relative_path = os.path.relpath(resolved_path, task_dir).replace("\\", "/")
    uri_path = f"tasks/{relative_path}"
    if endpoint:
        return f"{endpoint.rstrip('/')}/{uri_path}"
    return f"/{uri_path}"


def _parse_byte_range(
    range_header: str | None, file_size: int, request_id: str
) -> tuple[int, int]:
    """解析单段 HTTP Range,并把无效或越界请求稳定转换成 416。"""
    if file_size <= 0:
        raise HttpException(
            task_id=request_id,
            status_code=416,
            message=f"{request_id}: requested range is not satisfiable",
        )

    if not range_header:
        return 0, file_size - 1

    try:
        # 视频播放器这里只需要单段 bytes range。拒绝多段请求可以避免返回体
        # 与 Content-Range 不一致,也避免异常字符串落入 int() 产生 500。
        if not range_header.startswith("bytes=") or "," in range_header:
            raise ValueError("unsupported range format")
        start_text, end_text = range_header[6:].split("-", 1)
        if not start_text and not end_text:
            raise ValueError("empty range")

        if not start_text:

View on GitHub (pinned to 1f9f19c202)

Solutions

  1. Verify the task actually finished successfully and re-check the artifact path returned by GET /tasks/{id}; regenerate if the output is empty.
  2. On the server, inspect the file with ls -l / os.path.getsize to confirm it is 0 bytes, then re-run generation.
  3. Guard clients: treat 416 on a stream as a corrupt-artifact signal, not a range negotiation problem.

Example fix

# before
resp = requests.get(stream_url, headers={"Range": "bytes=0-1023"})
assert resp.status_code == 206

# after
size = os.path.getsize(local_artifact) if local_artifact else None
if size is None or size <= 0:
    task = requests.get(f"{base}/api/v1/tasks/{task_id}", headers=h).json()
    stream_url = task["data"]["videos"][0]  # fresh artifact
resp = requests.get(stream_url, headers={"Range": "bytes=0-1023"})
Defensive patterns

Strategy: validation

Validate before calling

size = os.path.getsize(video_path)  # or Content-Length from a plain GET
if size <= 0:
    raise ValueError("artifact is empty; regenerate the video")

Type guard

def has_satisfiable_range(file_size: int) -> bool:
    return isinstance(file_size, int) and file_size > 0

Try / catch

resp = requests.get(url, headers={"Range": "bytes=0-1023"})
if resp.status_code == 416:
    # empty or corrupt artifact; re-fetch task and regenerate, don't re-negotiate ranges
    task = get_task(task_id); url = task["videos"][0]

Prevention

When it happens

Trigger: GET /api/v1/stream/<path> (with or without a Range header) where the resolved on-disk file is 0 bytes; a generation task produced an empty/partial output file; a file truncated externally after task completion.

Common situations: Upstream video generation silently wrote an empty file; disk-full during write; task marked complete before the artifact was flushed; deleting/truncating artifacts out from under a running server.

Related errors


AI-assisted analysis of harry0703/MoneyPrinterTurbo@1f9f19c202 (2026-08-14). Data as JSON: /api/errors/e05aa3bf8b7195f7. Report an issue: GitHub.