abi/screenshot-to-code · error · HTTPException
max_age_days must be >= 1
Error message
max_age_days must be >= 1
What it means
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.
Source
Thrown at backend/routes/agent_runs.py:231
@router.get("/agent-runs/{run_id}/assets/{filename}")
async def get_agent_run_asset(run_id: str, filename: str) -> FileResponse:
_fetch_run(run_id)
assets_dir = os.path.join(get_agent_runs_directory(), run_id, "assets")
# basename() strips any traversal components before the containment check.
safe_name = os.path.basename(filename)
path = os.path.realpath(os.path.join(assets_dir, safe_name))
if not path.startswith(os.path.realpath(assets_dir) + os.sep):
raise HTTPException(status_code=400, detail="Invalid asset path")
if not os.path.isfile(path):
raise HTTPException(status_code=404, detail="Asset not found")
return FileResponse(path)
@router.post("/agent-runs/prune", response_model=PruneAgentRunsResponse)
async def prune_agent_runs(request: PruneAgentRunsRequest) -> PruneAgentRunsResponse:
if request.max_age_days < 1:
raise HTTPException(status_code=400, detail="max_age_days must be >= 1")
runs_directory = get_agent_runs_directory()
if not os.path.isdir(runs_directory):
return PruneAgentRunsResponse(deleted_count=0, freed_bytes=0)
cutoff_timestamp = (
datetime.now() - timedelta(days=request.max_age_days)
).timestamp()
deleted_count = 0
freed_bytes = 0
deleted_run_ids: list[str] = []
for entry in os.scandir(runs_directory):
if not entry.is_dir() or not RUN_ID_PATTERN.match(entry.name):
continue
if entry.stat().st_mtime >= cutoff_timestamp:
continueView on GitHub (pinned to d026163f58)
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.
Example fix
# before
requests.post(url + "/agent-runs/prune", json={"max_age_days": 0}) # 400
# after
requests.post(url + "/agent-runs/prune", json={"max_age_days": max(1, days)}) Defensive patterns
Strategy: validation
Validate before calling
max_age_days = max(1, int(max_age_days))
if max_age_days < 1:
raise ValueError("max_age_days must be >= 1")
resp = requests.post(url + "/agent-runs/prune", json={"max_age_days": max_age_days}) Type guard
def is_valid_prune_days(value: object) -> bool:
return isinstance(value, int) and not isinstance(value, bool) and value >= 1 Try / catch
try:
resp = requests.post(url + "/agent-runs/prune", json={"max_age_days": days})
resp.raise_for_status()
except requests.HTTPError as e:
if e.response is not None and e.response.status_code == 400:
raise ValueError("prune rejected: max_age_days must be >= 1") from e
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid run id
- Invalid asset path
- Design system name is required
- Invalid eval set name: {set_name!r}
- Not a set image: {filename!r}
AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14).
Data as JSON: /api/errors/d83d54ccb6f9f1ed.
Report an issue: GitHub.