{"record":{"id":"d83d54ccb6f9f1ed","repo":"abi/screenshot-to-code","slug":"max-age-days-must-be-1","errorCode":null,"errorMessage":"max_age_days must be >= 1","messagePattern":"max_age_days must be >= 1","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"backend/routes/agent_runs.py","lineNumber":231,"sourceCode":"\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:\n    if request.max_age_days < 1:\n        raise HTTPException(status_code=400, detail=\"max_age_days must be >= 1\")\n\n    runs_directory = get_agent_runs_directory()\n    if not os.path.isdir(runs_directory):\n        return PruneAgentRunsResponse(deleted_count=0, freed_bytes=0)\n\n    cutoff_timestamp = (\n        datetime.now() - timedelta(days=request.max_age_days)\n    ).timestamp()\n\n    deleted_count = 0\n    freed_bytes = 0\n    deleted_run_ids: list[str] = []\n\n    for entry in os.scandir(runs_directory):\n        if not entry.is_dir() or not RUN_ID_PATTERN.match(entry.name):\n            continue\n        if entry.stat().st_mtime >= cutoff_timestamp:\n            continue","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/agent_runs.py#L213-L249","documentation":"Raised by POST /agent-runs/prune (400) when the request body's max_age_days is less than 1. The endpoint refuses to prune runs younger than one day; zero or negative values are rejected up front rather than clamped. It is pure request validation — no state is touched.","triggerScenarios":"Calling POST /agent-runs/prune with {\"max_age_days\": 0} or a negative number, or omitting coercion so a string like \"0\" is parsed to 0 by the Pydantic request model.","commonSituations":"Automation scripts that compute max_age_days from a delta that can go negative (e.g. cutoff in the future); UIs defaulting the field to 0; attempts to 'prune everything' with 0.","solutions":["Pass an integer >= 1, e.g. {\"max_age_days\": 7}.","Clamp computed values in the caller: max(1, int(days)).","To delete everything regardless of age, compute a large max_age_days (e.g. 36500) instead of 0.","Check the PruneAgentRunsRequest model if unsure of the expected field name/type."],"exampleFix":"# before\nrequests.post(url + \"/agent-runs/prune\", json={\"max_age_days\": 0})  # 400\n\n# after\nrequests.post(url + \"/agent-runs/prune\", json={\"max_age_days\": max(1, days)})","handlingStrategy":"validation","validationCode":"max_age_days = max(1, int(max_age_days))\nif max_age_days < 1:\n    raise ValueError(\"max_age_days must be >= 1\")\nresp = requests.post(url + \"/agent-runs/prune\", json={\"max_age_days\": max_age_days})","typeGuard":"def is_valid_prune_days(value: object) -> bool:\n    return isinstance(value, int) and not isinstance(value, bool) and value >= 1","tryCatchPattern":"try:\n    resp = requests.post(url + \"/agent-runs/prune\", json={\"max_age_days\": days})\n    resp.raise_for_status()\nexcept requests.HTTPError as e:\n    if e.response is not None and e.response.status_code == 400:\n        raise ValueError(\"prune rejected: max_age_days must be >= 1\") from e\n    raise","preventionTips":["Clamp computed day values with max(1, n) before sending.","Use a large max_age_days (e.g. 36500) instead of 0 to prune everything.","Validate request bodies against PruneAgentRunsRequest before posting."],"tags":["fastapi","http-400","validation","prune","agent-runs"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}