calesthio/OpenMontage · error · RuntimeError

Completed but no video_url in output: {data}

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.

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)

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.