calesthio/OpenMontage · error · RuntimeError

Completed but no video_url in output

Error message

Completed but no video_url in output: {data}

What it means

Raised in the HeyGen execution poller when status is 'completed' but neither data.output.video.video_url nor data.output.video_url exists. HeyGen marked the execution done yet returned a payload the extractor cannot read, so there is no URL to download. Typically a response-shape variant (URL nested elsewhere, e.g. under output.video.url_list) or an empty completion.

Solutions

  1. Log the full data dict from the exception message and locate where the URL actually lives
  2. Extend the extractor chain (video.video_url, video_url, url_list[0], video.url)
  3. If output is genuinely empty, re-run the generation
  4. Check HeyGen changelog for output schema changes on the v1/v2 execution endpoints

Example fix

// before
video_url = data.get("output", {}).get("video", {}).get("video_url") or data.get("output", {}).get("video_url")
// after
out = data.get("output", {}) or {}
video_url = (
    (out.get("video") or {}).get("video_url")
    or out.get("video_url")
    or ((out.get("video") or {}).get("url_list") or [None])[0]
)
Defensive patterns

Strategy: try-catch

Validate before calling

null

Try / catch

try:
    url = wait_for_video(execution_id)
except RuntimeError as e:
    if "no video_url" in str(e):
        # one manual re-fetch: schema variants sometimes settle,
        # otherwise inspect data for alternate URL keys
        data = fetch_execution(execution_id)
        url = deep_first_url(data) or requeue_generation()
    raise

Prevention

When it happens

Trigger: Video Agent finishes with a payload where the URL key differs (url_list, video_urls, or per-scene URLs); a completed execution whose output was purged or never attached; preview vs final asset split in newer API versions.

Common situations: HeyGen API evolution adding new output layouts; long-retention windows expiring outputs; executions that completed with zero scenes.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/a1f168c70752c789. Report an issue: GitHub.

Appendix: source

Thrown at tools/video/_shared.py:407

    headers = {"X-Api-Key": api_key}
    url = f"https://api.heygen.com/v1/workflows/executions/{execution_id}"
    deadline = time.time() + timeout
    interval = 5.0

    while time.time() < deadline:
        response = requests.get(url, headers=headers, timeout=30)
        response.raise_for_status()
        data = response.json().get("data", {})
        status = data.get("status", "")

        if status == "completed":
            video_url = (
                data.get("output", {}).get("video", {}).get("video_url")
                or data.get("output", {}).get("video_url")
            )
            if video_url:
                return video_url
            raise RuntimeError(f"Completed but no video_url in output: {data}")

        if status in {"failed", "error"}:
            raise RuntimeError(f"HeyGen generation failed: {data.get('error', 'Unknown')}")

        time.sleep(min(interval, max(0.0, deadline - time.time())))
        interval = min(interval * 1.2, 30.0)

    raise TimeoutError(f"HeyGen execution {execution_id} timed out after {timeout}s")


def upload_image_fal(image_path: str) -> str:
    """Upload a local image to fal.ai storage and return a public URL."""
    import requests

    api_key = os.environ.get("FAL_KEY") or os.environ.get("FAL_AI_API_KEY")
    if not api_key:
        raise RuntimeError("FAL_KEY or FAL_AI_API_KEY required for image upload")

View on GitHub (pinned to 95e1c3d0ab)