{"record":{"id":"a8f9c3e2f820793c","repo":"abi/screenshot-to-code","slug":"run-has-no-captured-output","errorCode":null,"errorMessage":"Run has no captured output","messagePattern":"Run has no captured output","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"backend/routes/agent_runs.py","lineNumber":211,"sourceCode":"                except json.JSONDecodeError:\n                    # A crashed writer can leave a torn final line.\n                    continue\n                if not include_stream_deltas and event.get(\"type\") == \"stream_delta\":\n                    continue\n                events.append(event)\n    return AgentRunDetailResponse(run=run, events=events)\n\n\n@router.get(\"/agent-runs/{run_id}/output\")\nasync def get_agent_run_output(run_id: str) -> HTMLResponse:\n    _fetch_run(run_id)\n    run_dir = os.path.join(get_agent_runs_directory(), run_id)\n    for candidate in (\"final_selfcontained.html\", \"final.html\"):\n        path = os.path.join(run_dir, candidate)\n        if os.path.isfile(path):\n            with open(path, \"r\", encoding=\"utf-8\") as f:\n                return HTMLResponse(content=f.read())\n    raise HTTPException(status_code=404, detail=\"Run has no captured output\")\n\n\n@router.get(\"/agent-runs/{run_id}/assets/{filename}\")\nasync def get_agent_run_asset(run_id: str, filename: str) -> FileResponse:\n    _fetch_run(run_id)\n    assets_dir = os.path.join(get_agent_runs_directory(), run_id, \"assets\")\n    # basename() strips any traversal components before the containment check.\n    safe_name = os.path.basename(filename)\n    path = os.path.realpath(os.path.join(assets_dir, safe_name))\n    if not path.startswith(os.path.realpath(assets_dir) + os.sep):\n        raise HTTPException(status_code=400, detail=\"Invalid asset path\")\n    if not os.path.isfile(path):\n        raise HTTPException(status_code=404, detail=\"Asset not found\")\n    return FileResponse(path)\n\n\n@router.post(\"/agent-runs/prune\", response_model=PruneAgentRunsResponse)\nasync def prune_agent_runs(request: PruneAgentRunsRequest) -> PruneAgentRunsResponse:","sourceCodeStart":193,"sourceCodeEnd":229,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/agent_runs.py#L193-L229","documentation":"Raised by GET /agent-runs/{run_id}/output (404) when the run exists in the index but its directory contains neither final_selfcontained.html nor final.html. The route serves captured artifacts, so it 404s when generation never finished far enough to write an output file, or the files were deleted from disk even though the DB row remains.","triggerScenarios":"Calling /agent-runs/{run_id}/output for a run that is still in progress, failed mid-generation, or whose run directory was partially cleaned (DB row kept, HTML files removed).","commonSituations":"Polling the output URL while the agent is still running; a crashed/killed generation; disk cleanup that removed run directories but not the SQLite index; checking output of a run that only produced assets.","solutions":["Check the run's status via GET /agent-runs/{run_id} and retry once it is complete.","List the run directory under the runs_directory returned by GET /agent-runs and confirm final.html / final_selfcontained.html exist.","If files were deleted, prune the run (POST /agent-runs/prune) so the stale DB row is removed.","If generation failed, re-run the generation that produces the output."],"exampleFix":"# before\nresp = client.get(f\"/agent-runs/{run_id}/output\")  # 404 'Run has no captured output'\n\n# after\ndetail = client.get(f\"/agent-runs/{run_id}\").json()\nif detail[\"run\"][\"status\"] != \"completed\":\n    wait_for_completion(run_id)\nresp = client.get(f\"/agent-runs/{run_id}/output\")","handlingStrategy":"validation","validationCode":"detail = client.get(f\"/agent-runs/{run_id}\").json()\nstatus = detail[\"run\"][\"status\"]\nif status != \"completed\":\n    raise RuntimeError(f\"run not finished (status={status}); no output yet\")","typeGuard":"def run_has_output(detail: dict) -> bool:\n    \"\"\"Heuristic: only completed runs capture final HTML output.\"\"\"\n    return detail[\"run\"].get(\"status\") == \"completed\"","tryCatchPattern":"try:\n    resp = client.get(f\"/agent-runs/{run_id}/output\")\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 404 and \"captured output\" in e.response.text:\n        resp = None  # still running or artifacts missing\n    else:\n        raise","preventionTips":["Poll run status until completion before fetching /output.","Treat a missing output file as 'not done yet or failed', and re-check status.","Prune runs whose directories were half-deleted so stale rows disappear."],"tags":["fastapi","http-404","artifacts","agent-runs","lifecycle"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}