charmbracelet/crush · error
get total stats: %w
Error message
get total stats: %w
What it means
gatherStats runs SQLC-generated queries against the session SQLite database. This error wraps a failure from queries.GetTotalStats, the aggregate totals query (sessions, tokens, message averages). Because the DB is opened with a busy timeout, the most common failure is the database file being locked or unavailable, or a schema mismatch after an upgrade.
Source
Thrown at internal/cmd/stats.go:547
})
sort.Slice(merged.ToolUsage, func(i, j int) bool {
return merged.ToolUsage[i].CallCount > merged.ToolUsage[j].CallCount
})
return merged
}
func gatherStats(ctx context.Context, conn *sql.DB) (*Stats, error) {
queries := db.New(conn)
stats := &Stats{
GeneratedAt: time.Now().UTC(),
}
// Total stats.
total, err := queries.GetTotalStats(ctx)
if err != nil {
return nil, fmt.Errorf("get total stats: %w", err)
}
stats.Total = TotalStats{
TotalSessions: total.TotalSessions,
TotalPromptTokens: toInt64(total.TotalPromptTokens),
TotalCompletionTokens: toInt64(total.TotalCompletionTokens),
TotalTokens: toInt64(total.TotalPromptTokens) + toInt64(total.TotalCompletionTokens),
TotalCost: toFloat64(total.TotalCost),
TotalMessages: toInt64(total.TotalMessages),
AvgTokensPerSession: toFloat64(total.AvgTokensPerSession),
AvgMessagesPerSession: toFloat64(total.AvgMessagesPerSession),
}
// Usage by day.
dailyUsage, err := queries.GetUsageByDay(ctx)
if err != nil {
return nil, fmt.Errorf("get usage by day: %w", err)
}
for _, d := range dailyUsage {View on GitHub (pinned to 7944b8e522)
Solutions
- Ensure no other crush process is running that holds the DB lock, then retry
- Verify the DB file exists in the data dir (default .crush/crush.db) and is readable by the current user
- If the schema may be stale or incompatible, remove/rename the DB (losing stats) or upgrade to a matching crush version
- Move the data dir off NFS to a local filesystem if locking errors persist
Example fix
// before crush stats # Error: database is locked // after pkill -f 'crush' && crush stats # or copy .crush locally if it sits on NFS
Defensive patterns
Strategy: retry
Validate before calling
# Pre-check DB accessibility before running stats
db="${CRUSH_DATA_DIR:-.crush}/crush.db"
[ -f "$db" ] || { echo "DB missing: $db" >&2; exit 1; }
[ -r "$db" ] || { echo "DB unreadable: $db" >&2; exit 1; }
sqlite3 "$db" 'PRAGMA integrity_check;' | grep -q '^ok' || { echo "DB corrupt" >&2; exit 1; } Try / catch
if err := gatherStats(ctx, dbPath); err != nil {
var lockedErr sqlite3.Error
if errors.As(err, &lockedErr) && errors.Is(err, sqlite3.ErrLocked) || strings.Contains(err.Error(), "locked") {
time.Sleep(500 * time.Millisecond)
return gatherStats(ctx, dbPath) // retry after writer releases lock
}
return err
} Prevention
- Don't run `crush stats` while a crush session is actively writing
- Keep the data dir on a local filesystem, not NFS (SQLite locking is unreliable there)
- Avoid sudo runs that change DB file ownership
- Back up crush.db before upgrading or deleting it
- Check `PRAGMA integrity_check` after any crash
When it happens
Trigger: GetTotalStats fails: the SQLite file cannot be read (permissions, missing file), the DB is locked by another crush process, disk I/O error, or the schema is from an incompatible version (missing tables/columns).
Common situations: Running `crush stats` while another crush instance holds a write lock; pointing --data-dir at a directory without a crush.db; downgrading crush so stats tables no longer match; NFS-mounted home dirs where SQLite locking is unreliable.
Related errors
- get usage by day: %w
- get usage by model: %w
- get usage by hour: %w
- get usage by day of week: %w
- get recent activity: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/fa067b98ff07f9f0.
Report an issue: GitHub.