odysseus-dev/odysseus · error · HTTPException
Failed to retrieve database statistics
Error message
Failed to retrieve database statistics
What it means
Raised (HTTP 500) by GET /api/db/stats (admin-gated) when importing core.database.get_detailed_stats or executing it throws. Unlike the logs endpoint, the raw exception text is NOT returned — only the generic message — but the cause is logged as 'DB stats error'. Typical causes: SQLite database file missing/locked/corrupted, schema mismatch after an upgrade, or an import failure inside core.database.
Source
Thrown at routes/diagnostics_routes.py:64
tail_lines = [line.rstrip('\r\n') for line in tail_lines]
return {
"status": "success",
"logs": tail_lines
}
except Exception as e:
logger.error(f"Diagnostics logs retrieval error: {e}")
raise HTTPException(500, f"Failed to retrieve logs: {str(e)}")
@router.get("/api/db/stats")
async def get_database_stats(request: Request) -> Dict[str, Any]:
require_admin(request)
try:
from core.database import get_detailed_stats
return get_detailed_stats()
except Exception as e:
logger.error(f"DB stats error: {e}")
raise HTTPException(500, "Failed to retrieve database statistics")
@router.get("/api/rag/stats")
async def get_rag_stats(request: Request) -> Dict[str, Any]:
require_admin(request)
if rag_available and rag_manager:
return rag_manager.get_stats()
return {"error": "RAG system not available"}
@router.get("/api/test/youtube")
async def test_youtube(request: Request, url: str) -> Dict[str, Any]:
require_admin(request)
try:
video_id = extract_youtube_id(url)
if not video_id:
return {"error": "Invalid YouTube URL"}
data = await extract_transcript_async(url, video_id)
return {View on GitHub (pinned to f9235ebbf1)
Solutions
- Read the server log for the 'DB stats error: ...' line to get the real exception.
- If it is 'database is locked', stop competing processes (second server instance, manual sqlite3 session) or enable WAL mode.
- If it is 'no such table/column', run the app's DB init/migration path (init_db) so the schema is brought up to date.
- If the file was moved/deleted, restore it or let the app recreate it, then restart.
Defensive patterns
Strategy: try-catch
Validate before calling
# Pre-flight: DB reachable and schema present before the dashboard calls stats
from core.database import SessionLocal
with SessionLocal() as db:
db.execute(text('SELECT 1')) Try / catch
try { const stats = await get('/api/db/stats'); } catch (e) { if (e.status === 500 && /database statistics/.test(e.message)) { showBanner('DB stats unavailable — check server log for DB stats error'); return null; } throw e; } Prevention
- Run only one writer process against the SQLite file, or enable WAL.
- Run init_db migrations after every app upgrade before serving requests.
When it happens
Trigger: GET /api/db/stats while the database file is locked by another writer (SQLite BUSY), the DB was deleted or moved after startup, a migration left the schema without a table/column get_detailed_stats queries, or core.database import fails (missing optional dependency).
Common situations: Two server instances pointed at the same SQLite file; DB directory on a network mount with flaky locking; app upgraded to a version whose get_detailed_stats expects a column the old DB lacks; database file permissions changed.
Related errors
- Failed to delete calendar
- Failed to retrieve logs: {str(e)}
- data.error || ('HTTP ' + (data.status || 500))
- model returned no rewritten text
- Session {session_id} not found
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/de90ddda7bd28db2.
Report an issue: GitHub.