charmbracelet/crush · error
get tool usage: %w
Error message
get tool usage: %w
What it means
gatherStats in internal/cmd/stats.go wraps a failure from the sqlc-generated query queries.GetToolUsage(ctx), which aggregates per-tool call counts from the SQLite session/message tables. The %w wrapping preserves the underlying database error. It is thrown while building the stats report for `crush stats`.
Source
Thrown at internal/cmd/stats.go:642
stats.RecentActivity = append(stats.RecentActivity, DailyActivity{
Day: fmt.Sprintf("%v", r.Day),
SessionCount: r.SessionCount,
TotalTokens: nullFloat64ToInt64(r.TotalTokens),
Cost: r.Cost.Float64,
})
}
// Average response time.
avgResp, err := queries.GetAverageResponseTime(ctx)
if err != nil {
return nil, fmt.Errorf("get average response time: %w", err)
}
stats.AvgResponseTimeMs = toFloat64(avgResp) * 1000
// Tool usage.
toolUsage, err := queries.GetToolUsage(ctx)
if err != nil {
return nil, fmt.Errorf("get tool usage: %w", err)
}
for _, t := range toolUsage {
if name, ok := t.ToolName.(string); ok && name != "" {
stats.ToolUsage = append(stats.ToolUsage, ToolUsage{
ToolName: name,
CallCount: t.CallCount,
})
}
}
// Hour/day heatmap.
heatmap, err := queries.GetHourDayHeatmap(ctx)
if err != nil {
return nil, fmt.Errorf("get hour day heatmap: %w", err)
}
for _, h := range heatmap {
stats.HourDayHeatmap = append(stats.HourDayHeatmap, HourDayHeatmapPt{
DayOfWeek: int(h.DayOfWeek),View on GitHub (pinned to 7944b8e522)
Solutions
- Read the wrapped cause (%w) to identify the exact SQLite failure (locked, corrupt, no such table).
- Close other crush instances holding a lock on the database, then retry `crush stats`.
- If the DB is corrupt, restore from a backup or move it aside and let crush recreate it (loses history).
- Ensure the DB schema is current: run crush once normally so migrations apply, then rerun stats.
Example fix
// before: opaque failure
stats, err := gatherStats(ctx, queries)
// after: inspect wrapped cause
if err != nil {
var sqliteErr sqlite.Error
if errors.As(err, &sqliteErr) {
log.Printf("sqlite error code %d: %v", sqliteErr.Code, sqliteErr)
}
return err
} Defensive patterns
Strategy: try-catch
Try / catch
stats, err := gatherStats(ctx, queries)
if err != nil {
var se sqlite.Error
if errors.As(err, &se) {
switch se.Code {
case sqlite.ErrLocked:
// retry after closing other crush instances
default:
return fmt.Errorf("stats unavailable: %w", err)
}
}
return err
} Prevention
- Avoid running multiple crush instances that write the same DB concurrently.
- Keep the DB schema migrated before using `crush stats`.
- Back up the SQLite file periodically to recover from corruption.
- Always inspect the wrapped %w cause rather than the message alone.
When it happens
Trigger: Running `crush stats` when the SQLite query for tool usage fails: DB file locked/corrupt, disk I/O error, missing schema (unmigrated DB), or a query timeout.
Common situations: Corrupt or partially migrated SQLite database in the config/data directory; another crush process holding a write lock; read-only filesystem or full disk.
Related errors
- get hour day heatmap: %w
- failed to connect to database: %w
- failed to connect to database: %w
- no data available: no sessions found in database
- get total stats: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/a132bf2ee08fb482.
Report an issue: GitHub.