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

  1. Re-fetch the report list (the listing endpoint) and retry with a filename from the fresh response
  2. Confirm the file actually exists: ls the directory returned by get_prompt_reports_directory() and compare with the requested filename (case-sensitive on Linux)
  3. Check whether /prompt-reports/prune ran recently and deleted it; adjust max_age_days if retention is too aggressive
  4. 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

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.