{"record":{"id":"bb01c4c604622c62","repo":"abi/screenshot-to-code","slug":"invalid-run-id","errorCode":null,"errorMessage":"Invalid run id","messagePattern":"Invalid run id","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/agent_runs.py","lineNumber":137,"sourceCode":"    data = dict(zip(_RUN_COLUMNS, row))\n    data[\"has_unpriced_calls\"] = bool(data[\"has_unpriced_calls\"])\n    return AgentRunSummary(**data)\n\n\ndef _directory_size_bytes(path: str) -> int:\n    total = 0\n    for root, _, files in os.walk(path):\n        for name in files:\n            try:\n                total += os.path.getsize(os.path.join(root, name))\n            except OSError:\n                continue\n    return total\n\n\ndef _fetch_run(run_id: str) -> AgentRunSummary:\n    if not RUN_ID_PATTERN.match(run_id):\n        raise HTTPException(status_code=400, detail=\"Invalid run id\")\n    if not os.path.isfile(get_agent_runs_db_path()):\n        raise HTTPException(status_code=404, detail=\"No runs recorded\")\n    conn = open_index_db()\n    try:\n        row = conn.execute(\n            f\"SELECT {', '.join(_RUN_COLUMNS)} FROM runs WHERE run_id = ?\",\n            (run_id,),\n        ).fetchone()\n    finally:\n        conn.close()\n    if row is None:\n        raise HTTPException(status_code=404, detail=\"Run not found\")\n    return _row_to_summary(row)\n\n\n@router.get(\"/agent-runs\", response_model=AgentRunListResponse)\nasync def list_agent_runs(limit: int = 200) -> AgentRunListResponse:\n    runs_directory = get_agent_runs_directory()","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/agent_runs.py#L119-L155","documentation":"Thrown by _fetch_run() in the agent-runs router when the run_id path parameter does not match RUN_ID_PATTERN (backend/fs_logging/agent_runs.py:52: ^run_\\d{8}_\\d{6}_[0-9a-f]{8}$, e.g. run_20260814_153000_ab12cd34). It is a 400 FastAPI HTTPException raised before any database or filesystem access, so the run was never looked up. The strict pattern exists because run_id is joined into filesystem paths, so it doubles as path-traversal protection.","triggerScenarios":"Any GET /agent-runs/{run_id}, /agent-runs/{run_id}/output, or /agent-runs/{run_id}/assets/{filename} call where run_id lacks the run_YYYYMMDD_HHMMMM_8hexchars shape: truncated ids, ids copied with a missing segment, URL-decoded ids with stray slashes, or entirely fabricated ids.","commonSituations":"Frontend passes a stale or hand-edited run id; a caller reconstructs the id from a timestamp instead of reading it from the list endpoint; the id is copy-pasted with whitespace; a script iterates directory names that do not follow the run_ naming convention.","solutions":["Fetch ids from GET /agent-runs and pass run.run_id verbatim instead of constructing or editing ids by hand.","Verify the id against the regex ^run_\\d{8}_\\d{6}_[0-9a-f]{8}$ before issuing the request.","Check for whitespace or URL-encoding damage (e.g. %20, unencoded underscores) in the id you are sending.","If you maintain a producer of run ids, ensure it formats them as run_ + date + time + 8 lowercase hex chars."],"exampleFix":"# before\nresp = client.get(f\"/agent-runs/{run_id}\")  # run_id = 'run-2026-08-14' -> 400\n\n# after\nimport re\nRUN_ID_RE = re.compile(r\"^run_\\d{8}_\\d{6}_[0-9a-f]{8}$\")\nif not RUN_ID_RE.match(run_id):\n    raise ValueError(f\"malformed run id: {run_id!r}\nresp = client.get(f\"/agent-runs/{run_id}\")","handlingStrategy":"validation","validationCode":"import re\nRUN_ID_RE = re.compile(r\"^run_\\d{8}_\\d{6}_[0-9a-f]{8}$\")\n\ndef is_valid_run_id(run_id: str) -> bool:\n    return bool(RUN_ID_RE.match(run_id))\n\n# before the call\nif not is_valid_run_id(run_id):\n    raise ValueError(f\"malformed run id: {run_id!r}\")","typeGuard":"def is_valid_run_id(run_id: str) -> bool:\n    \"\"\"True when run_id matches run_YYYYMMDD_HHMMSS_xxxxxxxx (8 lowercase hex).\"\"\"\n    return bool(re.match(r\"^run_\\d{8}_\\d{6}_[0-9a-f]{8}$\", run_id))","tryCatchPattern":"try:\n    resp = client.get(f\"/agent-runs/{run_id}\")\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and \"Invalid run id\" in e.response.text:\n        raise ValueError(f\"bad run id format: {run_id!r}\") from e\n    raise","preventionTips":["Always source run ids from GET /agent-runs responses, never construct them by hand.","Validate against the run_ pattern before making detail requests.","Strip whitespace from ids parsed out of logs or the DOM."],"tags":["fastapi","validation","http-400","agent-runs","path-traversal"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}