{"record":{"id":"9976441eae496b02","repo":"charmbracelet/crush","slug":"get-usage-by-hour-w","errorCode":null,"errorMessage":"get usage by hour: %w","messagePattern":"get usage by hour: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/stats.go","lineNumber":594,"sourceCode":"\t}\n\n\t// Usage by model.\n\tmodelUsage, err := queries.GetUsageByModel(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get usage by model: %w\", err)\n\t}\n\tfor _, m := range modelUsage {\n\t\tstats.UsageByModel = append(stats.UsageByModel, ModelUsage{\n\t\t\tModel:        m.Model,\n\t\t\tProvider:     m.Provider,\n\t\t\tMessageCount: m.MessageCount,\n\t\t})\n\t}\n\n\t// Usage by hour.\n\thourlyUsage, err := queries.GetUsageByHour(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get usage by hour: %w\", err)\n\t}\n\tfor _, h := range hourlyUsage {\n\t\tstats.UsageByHour = append(stats.UsageByHour, HourlyUsage{\n\t\t\tHour:         int(h.Hour),\n\t\t\tSessionCount: h.SessionCount,\n\t\t})\n\t}\n\n\t// Usage by day of week.\n\tdowUsage, err := queries.GetUsageByDayOfWeek(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"get usage by day of week: %w\", err)\n\t}\n\tfor _, d := range dowUsage {\n\t\tstats.UsageByDayOfWeek = append(stats.UsageByDayOfWeek, DayOfWeekUsage{\n\t\t\tDayOfWeek:        int(d.DayOfWeek),\n\t\t\tDayName:          dayNames[int(d.DayOfWeek)],\n\t\t\tSessionCount:     d.SessionCount,","sourceCodeStart":576,"sourceCodeEnd":612,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/stats.go#L576-L612","documentation":"gatherStats fetches hourly usage distribution via queries.GetUsageByHour and wraps any SQL failure with this message. Same root causes as the other gatherStats queries: the SQLite database must be open, unlocked, and schema-compatible for the query to succeed.","triggerScenarios":"GetUsageByHour fails: DB file locked, missing, unreadable, corrupted, or the prepared statement fails due to schema drift between crush versions.","commonSituations":"DB held by a long-running crush session; .crush on a flaky network mount; interrupted upgrade leaving partial migration; --data-dir pointing at a directory without a valid crush.db.","solutions":["Ensure no concurrent crush process holds the write lock, then rerun","Confirm the DB exists and migrations are complete for your crush version","If corrupted, remove or restore the DB and regenerate stats","Use a local (non-NFS) data directory for reliable SQLite behavior"],"exampleFix":"// before\ncrush stats  # get usage by hour: database is locked (another session running)\n// after\nwait for/close the running crush session, then: crush stats","handlingStrategy":"retry","validationCode":"db=\"${CRUSH_DATA_DIR:-.crush}/crush.db\"\nif fuser \"$db\" >/dev/null 2>&1; then echo \"DB in use; wait and retry\" >&2; exit 1; fi\nsqlite3 \"$db\" 'PRAGMA integrity_check;' | grep -q ok || { echo \"Corrupt DB\" >&2; exit 1; }","typeGuard":null,"tryCatchPattern":"if err := gatherStats(ctx, dbPath); err != nil {\n    if strings.Contains(err.Error(), \"get usage by hour\") {\n        select {\n        case <-time.After(time.Second):\n            return gatherStats(ctx, dbPath) // one retry for transient lock\n        }\n    }\n    return err\n}","preventionTips":["Serialize crush usage per data dir; avoid concurrent writers","Use a local disk for the data dir","Restore from backup rather than using a partially-corrupt DB","Complete migrations (run the matching crush version) before stats","Check for disk-full conditions after crashes"],"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"}