abi/screenshot-to-code · error · HTTPException
Run not found
Error message
Run not found
What it means
Raised by _fetch_run() (404) after the run id passed the format check and the index DB exists, but the SELECT on the runs table returned no row. The id is well-formed but unknown: either the run never existed, was pruned via POST /agent-runs/prune, or its directory/DB entry was removed manually.
Source
Thrown at backend/routes/agent_runs.py:149
continue
return total
def _fetch_run(run_id: str) -> AgentRunSummary:
if not RUN_ID_PATTERN.match(run_id):
raise HTTPException(status_code=400, detail="Invalid run id")
if not os.path.isfile(get_agent_runs_db_path()):
raise HTTPException(status_code=404, detail="No runs recorded")
conn = open_index_db()
try:
row = conn.execute(
f"SELECT {', '.join(_RUN_COLUMNS)} FROM runs WHERE run_id = ?",
(run_id,),
).fetchone()
finally:
conn.close()
if row is None:
raise HTTPException(status_code=404, detail="Run not found")
return _row_to_summary(row)
@router.get("/agent-runs", response_model=AgentRunListResponse)
async def list_agent_runs(limit: int = 200) -> AgentRunListResponse:
runs_directory = get_agent_runs_directory()
if not os.path.isfile(get_agent_runs_db_path()):
return AgentRunListResponse(
runs=[], total_size_bytes=0, runs_directory=runs_directory
)
conn = open_index_db()
try:
rows = conn.execute(
f"SELECT {', '.join(_RUN_COLUMNS)} FROM runs "
"ORDER BY created_at DESC, run_id DESC LIMIT ?",
(max(1, min(limit, 1000)),),
).fetchall()View on GitHub (pinned to d026163f58)
Solutions
- Re-list runs with GET /agent-runs and use an id from the current response.
- If the run should still exist, check whether POST /agent-runs/prune deleted it (it returns deleted_run_ids).
- Inspect the runs table (sqlite3 on the DB path from GET /agent-runs) to confirm which ids are actually recorded.
- Make sure you are hitting the same backend instance/data directory that recorded the run.
Example fix
# before
resp = client.get(f"/agent-runs/{run_id}/output") # 404 'Run not found'
# after
runs = {r["run_id"] for r in client.get("/agent-runs").json()["runs"]}
if run_id not in runs:
run_id = sorted(runs)[-1] # fall back to newest run
resp = client.get(f"/agent-runs/{run_id}/output") Defensive patterns
Strategy: validation
Validate before calling
known = {r["run_id"] for r in client.get("/agent-runs").json()["runs"]}
if run_id not in known:
raise LookupError(f"run not in index: {run_id}; known={sorted(known)[:5]}") Type guard
def run_exists(run_id: str, listing: dict) -> bool:
return any(r["run_id"] == run_id for r in listing["runs"]) Try / catch
try:
detail = client.get(f"/agent-runs/{run_id}").raise_for_status().json()
except httpx.HTTPStatusError as e:
if e.response.status_code == 404 and "Run not found" in e.response.text:
detail = newest(client.get("/agent-runs").json()["runs"]) # or drop the id
else:
raise Prevention
- Treat ids from GET /agent-runs as the source of truth and re-list after prunes.
- Discard stored ids on 404 instead of retrying them.
- Watch prune responses (deleted_run_ids) to invalidate caches.
When it happens
Trigger: GET /agent-runs/{run_id}, /output, or /assets/{filename} with a syntactically valid id that is not in the runs table — e.g. an id from an old session after a prune, or a typo that still matches the pattern (wrong hex suffix).
Common situations: Stale id stored in a frontend/bookmark after prune ran; the DB was rebuilt from scratch losing old entries; concurrent deletion between list and detail fetch.
Related errors
- No runs recorded
- Run has no captured output
- Asset not found
- Eval set not found: {set_name}
- Eval set not found: {request.eval_set}
AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14).
Data as JSON: /api/errors/3e6ce876f8445ccd.
Report an issue: GitHub.