odysseus-dev/odysseus · error · HTTPException
Tidy failed: {e}
Error message
Tidy failed: {e} What it means
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.
Source
Thrown at routes/document/document_routes.py:962
.filter(Document.is_active == False)
.filter((Document.current_content == None) | (Document.current_content == ""))
)
inactive_q = _owner_session_filter(inactive_q, user)
inactive_docs = inactive_q.all()
for doc in inactive_docs:
db.delete(doc)
deleted += len(inactive_docs)
db.commit()
return {
"fixed_titles": fixed_titles,
"deleted": deleted,
"message": f"Fixed {fixed_titles} title{'s' if fixed_titles != 1 else ''}, removed {deleted} empty document{'s' if deleted != 1 else ''}",
}
except Exception as e:
db.rollback()
logger.error(f"Document tidy failed: {e}")
raise HTTPException(500, f"Tidy failed: {e}")
finally:
db.close()
# ---- POST /api/documents/ai-tidy — AI-powered cleanup of junk/test documents ----
@router.post("/api/documents/ai-tidy")
async def ai_tidy_documents(request: Request) -> Dict[str, Any]:
"""Use AI to judge if documents are junk/test/accidental, then delete them.
Caches verdicts so previously-reviewed docs are skipped."""
from src.task_endpoint import resolve_task_endpoint
from src.endpoint_resolver import resolve_endpoint
from src.llm_core import llm_call_async
user = get_current_user(request)
url, model, headers = resolve_task_endpoint(owner=user or None)
if not url or not model:
# Fall back to default endpoint
url, model, headers = resolve_endpoint("default", owner=user or None)
if not url or not model:View on GitHub (pinned to f9235ebbf1)
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.
Example fix
# before
resp = requests.post(f"{base}/api/documents/tidy")
resp.raise_for_status()
# after
resp = requests.post(f"{base}/api/documents/tidy")
if resp.status_code == 500:
logging.warning("tidy failed: %s", resp.json().get("detail"))
# safe to retry later — rollback left data intact
schedule_retry(delay=60) Defensive patterns
Strategy: retry
Try / catch
try:
r = requests.post(f"{base}/api/documents/tidy", timeout=120)
except requests.HTTPError as e:
if e.response.status_code == 500:
logging.warning("tidy failed: %s", e.response.json().get("detail"))
schedule_retry(delay=60) # rollback kept data intact; retry is safe
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Failed to delete calendar
- Failed to list calendars
- Failed to list events
- Failed to create event
- Failed to update event
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/da7ddbb512b1bcbc.
Report an issue: GitHub.