{"record":{"id":"61428a8c7e86478f","repo":"jamiepine/voicebox","slug":"capture-not-found","errorCode":null,"errorMessage":"Capture not found","messagePattern":"Capture not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"error","filePath":"backend/routes/captures.py","lineNumber":89,"sourceCode":"async def list_captures_endpoint(\n    limit: int = 50,\n    offset: int = 0,\n    db: Session = Depends(get_db),\n):\n    if limit < 1 or limit > 200:\n        raise HTTPException(status_code=400, detail=\"limit must be between 1 and 200\")\n    if offset < 0:\n        raise HTTPException(status_code=400, detail=\"offset must be >= 0\")\n\n    items, total = captures_service.list_captures(db, limit=limit, offset=offset)\n    return models.CaptureListResponse(items=items, total=total)\n\n\n@router.get(\"/captures/{capture_id}\", response_model=models.CaptureResponse)\nasync def get_capture_endpoint(capture_id: str, db: Session = Depends(get_db)):\n    capture = captures_service.get_capture(capture_id, db)\n    if not capture:\n        raise HTTPException(status_code=404, detail=\"Capture not found\")\n    return capture\n\n\n@router.get(\"/captures/{capture_id}/audio\")\nasync def get_capture_audio_endpoint(capture_id: str, db: Session = Depends(get_db)):\n    \"\"\"Stream the original capture audio file.\"\"\"\n    row = db.query(DBCapture).filter(DBCapture.id == capture_id).first()\n    if not row:\n        raise HTTPException(status_code=404, detail=\"Capture not found\")\n\n    audio_path = config.resolve_storage_path(row.audio_path)\n    if audio_path is None or not audio_path.exists():\n        raise HTTPException(status_code=404, detail=\"Audio file not found\")\n\n    return FileResponse(\n        audio_path,\n        media_type=\"audio/wav\",\n        filename=f\"capture_{capture_id}.wav\",","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/backend/routes/captures.py#L71-L107","documentation":"Returned as a 404 from GET /captures/{capture_id} when captures_service.get_capture returns a falsy value — no Capture row matches the id (or the service's response model validation produced None). The handler returns early before serializing. This is the canonical lookup-miss for a single capture.","triggerScenarios":"GET /captures/{capture_id} with an id that does not exist: deleted capture, typo, id from another deployment, or a just-created id not yet committed/visible.","commonSituations":"Client holds a capture id from a previous session after the capture was deleted; race where the client polls an id before the creating transaction committed; cross-environment id leakage (staging id sent to prod).","solutions":["Confirm capture_id against the current captures list.","Query the DB directly: SELECT * FROM captures WHERE id = ?.","If the capture was deleted, remove the stale reference from the client.","If expecting a just-created capture, retry briefly to account for commit/replication lag."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const list = await listCaptures();\nif (!list.items.some(c => c.id === captureId)) {\n  throw new Error('Capture id not in current list');\n}","typeGuard":"function captureExists(id: string, items: { id: string }[]): boolean {\n  return items.some(c => c.id === id);\n}","tryCatchPattern":"try {\n  const r = await fetch(`/captures/${id}`);\n  if (r.status === 404) { dropStaleCapture(id); return; }\n} catch (e) { showNetworkError(e); }","preventionTips":["Source capture ids from the live list, not cached state.","Remove deleted captures from the client immediately.","For just-created captures, retry briefly to cover commit lag."],"tags":["capture","not-found","fastapi"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}