{"record":{"id":"9bd5a2c591b0472e","repo":"odysseus-dev/odysseus","slug":"document-not-found-9bd5a2","errorCode":null,"errorMessage":"Document not found","messagePattern":"Document not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"routes/document/document_routes.py","lineNumber":476,"sourceCode":"            q = db.query(Document).filter(\n                Document.session_id == session_id\n            )\n            if user:\n                q = q.filter(or_(Document.owner == user, Document.owner.is_(None)))\n            docs = q.order_by(Document.created_at.desc()).all()\n            return [_doc_to_dict(d) for d in docs]\n        finally:\n            db.close()\n\n    # ---- GET /api/document/{doc_id} ----\n    @router.get(\"/api/document/{doc_id}\")\n    async def get_document(request: Request, doc_id: str) -> Dict[str, Any]:\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n            return _doc_to_dict(doc)\n        finally:\n            db.close()\n\n    # ---- POST /api/document/{doc_id}/archive — soft-archive / restore ----\n    @router.post(\"/api/document/{doc_id}/archive\")\n    async def archive_document(request: Request, doc_id: str, archived: bool = Query(True)) -> Dict[str, Any]:\n        user = get_current_user(request)\n        db = SessionLocal()\n        try:\n            doc = db.query(Document).filter(Document.id == doc_id).first()\n            if not doc:\n                raise HTTPException(404, \"Document not found\")\n            _verify_doc_owner(db, doc, user)\n            doc.archived = bool(archived)\n            db.commit()\n            return {\"ok\": True, \"id\": doc_id, \"archived\": doc.archived}","sourceCodeStart":458,"sourceCodeEnd":494,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L458-L494","documentation":"404 from GET /api/document/{doc_id}: no Document row matches the given id before ownership checks run. The id may be malformed, the document may have been soft-deleted by a path that filters it from this query, or it simply never existed.","triggerScenarios":"Client holds a stale doc id from an earlier session (document deleted via DELETE /api/document/{doc_id}); typo'd or truncated UUID in the URL; the doc exists but with an archived flag handled by a different listing route.","commonSituations":"Browser refresh on a doc editor after the doc was deleted elsewhere; deep links bookmarked to deleted docs; frontend state desync after bulk operations.","solutions":["Verify the doc id against GET /api/documents/library to confirm it still exists for this user.","If it was soft-deleted, restore it through the app's restore path instead of retrying the GET.","Fix the client to clear doc references when a delete/404 is returned rather than re-requesting the dead id.","Check the URL for encoding issues (truncated ids, stray characters)."],"exampleFix":"// before\nconst doc = await api.get(`/api/document/${id}`); // throws on 404\n// after\nconst doc = await api.get(`/api/document/${id}`).catch((e) => {\n  if (e.status === 404) { router.push('/library'); return null; }\n  throw e;\n});","handlingStrategy":"try-catch","validationCode":"const exists = (await api.get('/api/documents/library')).documents.some(d => d.id === id);\nif (!exists) { router.push('/library'); return; }","typeGuard":"function isDocId(id: unknown): id is string {\n  return typeof id === 'string' && /^[A-Za-z0-9_-]{6,}$/.test(id);\n}","tryCatchPattern":"try { return await api.get(`/api/document/${id}`); }\ncatch (e) { if (e.status === 404) { dropDocReference(id); return null; } throw e; }","preventionTips":["Purge doc references from client state on delete/404","Validate id shape before building URLs","Never auto-retry 404s — they are permanent for that id"],"tags":["http-404","rest","not-found","fastapi"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}