{"record":{"id":"214b312d1b141136","repo":"charmbracelet/crush","slug":"get-average-response-time-w","errorCode":null,"errorMessage":"get average response time: %w","messagePattern":"get average response time: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/stats.go","lineNumber":635,"sourceCode":"\n\t// Recent activity (last 30 days).\n\trecent, err := queries.GetRecentActivity(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get recent activity: %w\", err)\n\t}\n\tfor _, r := range recent {\n\t\tstats.RecentActivity = append(stats.RecentActivity, DailyActivity{\n\t\t\tDay:          fmt.Sprintf(\"%v\", r.Day),\n\t\t\tSessionCount: r.SessionCount,\n\t\t\tTotalTokens:  nullFloat64ToInt64(r.TotalTokens),\n\t\t\tCost:         r.Cost.Float64,\n\t\t})\n\t}\n\n\t// Average response time.\n\tavgResp, err := queries.GetAverageResponseTime(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get average response time: %w\", err)\n\t}\n\tstats.AvgResponseTimeMs = toFloat64(avgResp) * 1000\n\n\t// Tool usage.\n\ttoolUsage, err := queries.GetToolUsage(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get tool usage: %w\", err)\n\t}\n\tfor _, t := range toolUsage {\n\t\tif name, ok := t.ToolName.(string); ok && name != \"\" {\n\t\t\tstats.ToolUsage = append(stats.ToolUsage, ToolUsage{\n\t\t\t\tToolName:  name,\n\t\t\t\tCallCount: t.CallCount,\n\t\t\t})\n\t\t}\n\t}\n\n\t// Hour/day heatmap.","sourceCodeStart":617,"sourceCodeEnd":653,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/stats.go#L617-L653","documentation":"gatherStats fetches the average assistant response time via queries.GetAverageResponseTime and wraps any SQL failure with this message. The query computes a millisecond average from message timestamps; failures are database-level (lock, access, corruption, schema), not data-quality issues.","triggerScenarios":"GetAverageResponseTime fails: SQLite DB locked or unreadable, corrupt database file, or the prepared statement failing due to missing/mismatched schema in the sessions DB.","commonSituations":"Concurrent crush sessions contending for the write lock; crash-interrupted writes corrupting crush.db; schema/version mismatch; DB on network storage with unreliable locking.","solutions":["Ensure no other crush instance is writing, then rerun","Verify DB readability and integrity; restore or regenerate a corrupt DB","Match the crush binary version to the DB schema (reinstall/upgrade)","Move .crush to a local filesystem if it currently lives on NFS/network storage"],"exampleFix":"// before\ncrush stats  # get average response time: disk I/O error\n// after\nsqlite3 .crush/crush.db 'PRAGMA integrity_check;'  # if corrupt: restore or delete crush.db, then rerun","handlingStrategy":"retry","validationCode":"db=\"${CRUSH_DATA_DIR:-.crush}/crush.db\"\nif ! sqlite3 \"$db\" 'SELECT count(*) FROM messages;' >/dev/null 2>&1; then\n  echo \"Sessions DB not queryable (locked/missing/corrupt): $db\" >&2\n  exit 1\nfi","typeGuard":null,"tryCatchPattern":"if err := gatherStats(ctx, dbPath); err != nil {\n    if strings.Contains(err.Error(), \"get average response time\") && strings.Contains(err.Error(), \"locked\") {\n        time.Sleep(1 * time.Second)\n        return gatherStats(ctx, dbPath)\n    }\n    return err\n}","preventionTips":["Run stats when no crush session is actively writing","Keep the data dir on a local filesystem with working SQLite locks","Maintain the crush binary/DB schema in sync","Schedule backups of crush.db and verify integrity after crashes","Avoid sudo runs that alter DB ownership"],"tags":["sqlite","database","sql","cli"],"backgroundTag":"sqlite-database-locked","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}