charmbracelet/crush · error
failed to save todos: %w
Error message
failed to save todos: %w
What it means
Validating and building the todo list succeeded, but sessions.Save(ctx, currentSession) failed when persisting the session with its updated Todos. The DB error is wrapped with %w. The todo update is not applied.
Source
Thrown at internal/agent/tools/todos.go:101
justCompleted = append(justCompleted, item.Content)
}
}
if newStatus == session.TodoStatusInProgress {
if !existed || oldStatus != session.TodoStatusInProgress {
if item.ActiveForm != "" {
justStarted = item.ActiveForm
} else {
justStarted = item.Content
}
}
}
}
currentSession.Todos = todos
_, err = sessions.Save(ctx, currentSession)
if err != nil {
return fantasy.ToolResponse{}, fmt.Errorf("failed to save todos: %w", err)
}
response := "Todo list updated successfully.\n\n"
pendingCount := 0
inProgressCount := 0
for _, todo := range todos {
switch todo.Status {
case session.TodoStatusPending:
pendingCount++
case session.TodoStatusInProgress:
inProgressCount++
}
}
response += fmt.Sprintf("Status: %d pending, %d in progress, %d completed\n",
pendingCount, inProgressCount, completedCount)View on GitHub (pinned to 7944b8e522)
Solutions
- Inspect the wrapped cause to identify the DB issue (locked vs I/O vs constraint)
- Ensure no other process holds a write lock on the SQLite DB; enable busy timeout
- Check disk space and that the DB file/path is writable
- Retry the tool call after resolving the environment issue
Example fix
// before // save fails with 'database is locked' // after // close the second crush instance (or set busy_timeout) and retry the todos update
Defensive patterns
Strategy: retry
Try / catch
if err != nil && strings.Contains(err.Error(), "failed to save todos") {
if errors.Is(err, sqlite.ErrLocked) || isTransientIO(err) {
time.Sleep(backoff)
return retry()
}
return err
} Prevention
- Run only one process per SQLite DB, or configure a busy timeout
- Monitor free disk space where the DB lives
- Keep the DB path on a writable, non-read-only mount
- Check migrations are up to date before writing
When it happens
Trigger: SQLite write failure during the todos tool: database file locked by another process, disk full, read-only filesystem, schema/migration mismatch, or the session row being deleted between Get and Save.
Common situations: Running two Crush instances against the same DB; disk quota exhausted; DB file moved or corrupted mid-session; running on a read-only mount.
Related errors
- failed to commit transaction: %w
- failed to create user message: %w
- error creating file history: %w
- error creating file history: %w
- failed to get session: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/5d7f66e28e13210e.
Report an issue: GitHub.