abi/screenshot-to-code · warning · HTTPException
Report not found
Error message
Report not found
What it means
HTTPException(404, "Report not found") raised by GET /prompt-reports/content when the requested filename passes the PROMPT_REPORT_FILENAME_PATTERN check but os.path.isfile() finds no such file in the configured prompt-reports directory. The name is well-formed, but the file itself is absent by the time of the content request. Typical cause is a race with deletion (prune) or a reports-directory mismatch between listing and content endpoints.
Source
Thrown at backend/routes/prompt_reports.py:158
if os.path.isdir(run_logs_directory)
else 0
)
return PromptReportListResponse(
reports=reports,
total_size_bytes=total_size_bytes,
reports_directory=reports_directory,
)
@router.get("/prompt-reports/content")
async def get_prompt_report_content(filename: str) -> Any:
if PROMPT_REPORT_FILENAME_PATTERN.match(filename) is None:
raise HTTPException(status_code=400, detail="Invalid report filename")
filepath = os.path.join(get_prompt_reports_directory(), filename)
if not os.path.isfile(filepath):
raise HTTPException(status_code=404, detail="Report not found")
try:
with open(filepath, "r", encoding="utf-8") as f:
return json.load(f)
except (OSError, json.JSONDecodeError) as e:
raise HTTPException(status_code=500, detail=f"Failed to read report: {e}")
@router.post("/prompt-reports/prune", response_model=PrunePromptReportsResponse)
async def prune_prompt_reports(
request: PrunePromptReportsRequest,
) -> PrunePromptReportsResponse:
if request.max_age_days < 1:
raise HTTPException(status_code=400, detail="max_age_days must be >= 1")
run_logs_directory = get_run_logs_directory()
if not os.path.isdir(run_logs_directory):
return PrunePromptReportsResponse(deleted_count=0, freed_bytes=0)View on GitHub (pinned to d026163f58)
Solutions
- Re-fetch the report list (the listing endpoint) and retry with a filename from the fresh response
- Confirm the file actually exists: ls the directory returned by get_prompt_reports_directory() and compare with the requested filename (case-sensitive on Linux)
- Check whether /prompt-reports/prune ran recently and deleted it; adjust max_age_days if retention is too aggressive
- If running multiple instances, pin the reports directory to one absolute path via configuration so list and content endpoints agree
Example fix
// before (client)
const content = await fetch(`/prompt-reports/content?filename=${name}`); // 404 after prune
// after
let resp = await fetch(`/prompt-reports/content?filename=${name}`);
if (resp.status === 404) {
const { reports } = await fetch('/prompt-reports').then(r => r.json());
const fresh = reports.find(r => r.filename === name);
if (!fresh) throw new Error('Report was deleted');
resp = await fetch(`/prompt-reports/content?filename=${name}`);
} Defensive patterns
Strategy: validation
Validate before calling
import httpx
def fetch_report_content(base: str, filename: str) -> dict:
listing = httpx.get(f"{base}/prompt-reports", timeout=10).json()
names = {r["filename"] for r in listing.get("reports", [])}
if filename not in names:
raise KeyError(f"{filename} not in current report list; it may have been pruned")
return httpx.get(
f"{base}/prompt-reports/content", params={"filename": filename}, timeout=10
).json() Try / catch
try:
content = fetch(base, filename)
except httpx.HTTPStatusError as e:
if e.response.status_code == 404:
listing = relist(base) # refresh, then retry once with a fresh filename
... Prevention
- Always pick filenames from a fresh list response immediately before fetching content
- Treat reports as ephemeral: cache content, not filenames, if you need durability
- Schedule prune jobs and UI refresh so they do not interleave with user reads
When it happens
Trigger: GET /prompt-reports/content?filename=<valid-shaped-name>.json where the file was just deleted by POST /prompt-reports/prune, was written to a different get_prompt_reports_directory() than the one being read, or the client uses a stale filename from an old list response.
Common situations: UI lists reports, a prune job or retention policy runs concurrently, and the user clicks a report that no longer exists; running multiple backend instances with different working directories so the relative reports dir resolves differently.
Related errors
AI-assisted analysis of abi/screenshot-to-code@d026163f58 (2026-08-14).
Data as JSON: /api/errors/df7c603fe70eace0.
Report an issue: GitHub.