{"record":{"id":"da7ddbb512b1bcbc","repo":"odysseus-dev/odysseus","slug":"tidy-failed-e","errorCode":null,"errorMessage":"Tidy failed: {e}","messagePattern":"Tidy failed: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":500,"severity":"error","filePath":"routes/document/document_routes.py","lineNumber":962,"sourceCode":"                .filter(Document.is_active == False)\n                .filter((Document.current_content == None) | (Document.current_content == \"\"))\n            )\n            inactive_q = _owner_session_filter(inactive_q, user)\n            inactive_docs = inactive_q.all()\n            for doc in inactive_docs:\n                db.delete(doc)\n            deleted += len(inactive_docs)\n\n            db.commit()\n            return {\n                \"fixed_titles\": fixed_titles,\n                \"deleted\": deleted,\n                \"message\": f\"Fixed {fixed_titles} title{'s' if fixed_titles != 1 else ''}, removed {deleted} empty document{'s' if deleted != 1 else ''}\",\n            }\n        except Exception as e:\n            db.rollback()\n            logger.error(f\"Document tidy failed: {e}\")\n            raise HTTPException(500, f\"Tidy failed: {e}\")\n        finally:\n            db.close()\n\n    # ---- POST /api/documents/ai-tidy — AI-powered cleanup of junk/test documents ----\n    @router.post(\"/api/documents/ai-tidy\")\n    async def ai_tidy_documents(request: Request) -> Dict[str, Any]:\n        \"\"\"Use AI to judge if documents are junk/test/accidental, then delete them.\n        Caches verdicts so previously-reviewed docs are skipped.\"\"\"\n        from src.task_endpoint import resolve_task_endpoint\n        from src.endpoint_resolver import resolve_endpoint\n        from src.llm_core import llm_call_async\n\n        user = get_current_user(request)\n        url, model, headers = resolve_task_endpoint(owner=user or None)\n        if not url or not model:\n            # Fall back to default endpoint\n            url, model, headers = resolve_endpoint(\"default\", owner=user or None)\n        if not url or not model:","sourceCodeStart":944,"sourceCodeEnd":980,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/document/document_routes.py#L944-L980","documentation":"The 500 handler for POST /api/documents/tidy: any exception while scanning user documents, fixing empty titles, deleting broken/empty docs, or committing is caught, rolled back, logged as 'Document tidy failed: {e}', and returned as 'Tidy failed: {e}'. Because the handler formats the exception into the message, the appended text identifies the root cause.","triggerScenarios":"A broken document row (e.g. NULL session_id confusing the outerjoin/filter chain); DB lock or connection drop during the batch commit; a delete cascading into a table with a restricting foreign key; transiently invalid data (unparseable timestamps) raising during iteration.","commonSituations":"Running tidy concurrently with active document edits in another session; database behind a connection pool that timed out during the long batch; rows inserted by an older app version violating current model expectations.","solutions":["Read the '{e}' suffix in the 500 response and the server log line 'Document tidy failed: ...' for the true exception.","If it is a lock/timeout, ensure only one tidy runs at a time and increase the DB timeout or enable WAL for SQLite.","If a specific row breaks iteration, fix or remove that row (the log usually includes its identifier) and re-run tidy.","Run tidy when user write traffic is low to avoid races with concurrent edits."],"exampleFix":"# before\nresp = requests.post(f\"{base}/api/documents/tidy\")\nresp.raise_for_status()\n\n# after\nresp = requests.post(f\"{base}/api/documents/tidy\")\nif resp.status_code == 500:\n    logging.warning(\"tidy failed: %s\", resp.json().get(\"detail\"))\n    # safe to retry later — rollback left data intact\n    schedule_retry(delay=60)","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    r = requests.post(f\"{base}/api/documents/tidy\", timeout=120)\nexcept requests.HTTPError as e:\n    if e.response.status_code == 500:\n        logging.warning(\"tidy failed: %s\", e.response.json().get(\"detail\"))\n        schedule_retry(delay=60)  # rollback kept data intact; retry is safe\n    else:\n        raise","preventionTips":["Run tidy during low write traffic to avoid lock races with active edits.","Keep the DB timeout above the batch commit duration.","Treat tidy as idempotent — safe to re-run after failure."],"tags":["fastapi","http-500","maintenance","database","batch"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}