{"record":{"id":"a1f168c70752c789","repo":"calesthio/OpenMontage","slug":"completed-but-no-video-url-in-output-data","errorCode":null,"errorMessage":"Completed but no video_url in output: {data}","messagePattern":"Completed but no video_url in output: (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/video/_shared.py","lineNumber":407,"sourceCode":"    headers = {\"X-Api-Key\": api_key}\n    url = f\"https://api.heygen.com/v1/workflows/executions/{execution_id}\"\n    deadline = time.time() + timeout\n    interval = 5.0\n\n    while time.time() < deadline:\n        response = requests.get(url, headers=headers, timeout=30)\n        response.raise_for_status()\n        data = response.json().get(\"data\", {})\n        status = data.get(\"status\", \"\")\n\n        if status == \"completed\":\n            video_url = (\n                data.get(\"output\", {}).get(\"video\", {}).get(\"video_url\")\n                or data.get(\"output\", {}).get(\"video_url\")\n            )\n            if video_url:\n                return video_url\n            raise RuntimeError(f\"Completed but no video_url in output: {data}\")\n\n        if status in {\"failed\", \"error\"}:\n            raise RuntimeError(f\"HeyGen generation failed: {data.get('error', 'Unknown')}\")\n\n        time.sleep(min(interval, max(0.0, deadline - time.time())))\n        interval = min(interval * 1.2, 30.0)\n\n    raise TimeoutError(f\"HeyGen execution {execution_id} timed out after {timeout}s\")\n\n\ndef upload_image_fal(image_path: str) -> str:\n    \"\"\"Upload a local image to fal.ai storage and return a public URL.\"\"\"\n    import requests\n\n    api_key = os.environ.get(\"FAL_KEY\") or os.environ.get(\"FAL_AI_API_KEY\")\n    if not api_key:\n        raise RuntimeError(\"FAL_KEY or FAL_AI_API_KEY required for image upload\")\n","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/calesthio/OpenMontage/blob/95e1c3d0ab93482159818560f6a8c8e866b9139f/tools/video/_shared.py#L389-L425","documentation":"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.","triggerScenarios":"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.","commonSituations":"HeyGen API evolution adding new output layouts; long-retention windows expiring outputs; executions that completed with zero scenes.","solutions":["Log the full data dict from the exception message and locate where the URL actually lives","Extend the extractor chain (video.video_url, video_url, url_list[0], video.url)","If output is genuinely empty, re-run the generation","Check HeyGen changelog for output schema changes on the v1/v2 execution endpoints"],"exampleFix":"// before\nvideo_url = data.get(\"output\", {}).get(\"video\", {}).get(\"video_url\") or data.get(\"output\", {}).get(\"video_url\")\n// after\nout = data.get(\"output\", {}) or {}\nvideo_url = (\n    (out.get(\"video\") or {}).get(\"video_url\")\n    or out.get(\"video_url\")\n    or ((out.get(\"video\") or {}).get(\"url_list\") or [None])[0]\n)","handlingStrategy":"try-catch","validationCode":"null","typeGuard":null,"tryCatchPattern":"try:\n    url = wait_for_video(execution_id)\nexcept RuntimeError as e:\n    if \"no video_url\" in str(e):\n        # one manual re-fetch: schema variants sometimes settle,\n        # otherwise inspect data for alternate URL keys\n        data = fetch_execution(execution_id)\n        url = deep_first_url(data) or requeue_generation()\n    raise","preventionTips":["Log completed-but-unparseable payloads whole; they encode the new schema","Version-pin against HeyGen API change announcements","Centralize the URL-extraction chain so one fix covers all callers"],"tags":["heygen","video-generation","response-parsing","api-schema"],"backgroundTag":null,"analyzedSha":"95e1c3d0ab93482159818560f6a8c8e866b9139f","analyzedAt":"2026-08-15T06:31:20.014Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}