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

  1. Re-list runs with GET /agent-runs and use an id from the current response.
  2. If the run should still exist, check whether POST /agent-runs/prune deleted it (it returns deleted_run_ids).
  3. Inspect the runs table (sqlite3 on the DB path from GET /agent-runs) to confirm which ids are actually recorded.
  4. 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

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


AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14). Data as JSON: /api/errors/3e6ce876f8445ccd. Report an issue: GitHub.