{"record":{"id":"5d7f66e28e13210e","repo":"charmbracelet/crush","slug":"failed-to-save-todos-w","errorCode":null,"errorMessage":"failed to save todos: %w","messagePattern":"failed to save todos: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/tools/todos.go","lineNumber":101,"sourceCode":"\t\t\t\t\t\tjustCompleted = append(justCompleted, item.Content)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif newStatus == session.TodoStatusInProgress {\n\t\t\t\t\tif !existed || oldStatus != session.TodoStatusInProgress {\n\t\t\t\t\t\tif item.ActiveForm != \"\" {\n\t\t\t\t\t\t\tjustStarted = item.ActiveForm\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tjustStarted = item.Content\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tcurrentSession.Todos = todos\n\t\t\t_, err = sessions.Save(ctx, currentSession)\n\t\t\tif err != nil {\n\t\t\t\treturn fantasy.ToolResponse{}, fmt.Errorf(\"failed to save todos: %w\", err)\n\t\t\t}\n\n\t\t\tresponse := \"Todo list updated successfully.\\n\\n\"\n\n\t\t\tpendingCount := 0\n\t\t\tinProgressCount := 0\n\n\t\t\tfor _, todo := range todos {\n\t\t\t\tswitch todo.Status {\n\t\t\t\tcase session.TodoStatusPending:\n\t\t\t\t\tpendingCount++\n\t\t\t\tcase session.TodoStatusInProgress:\n\t\t\t\t\tinProgressCount++\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tresponse += fmt.Sprintf(\"Status: %d pending, %d in progress, %d completed\\n\",\n\t\t\t\tpendingCount, inProgressCount, completedCount)","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/tools/todos.go#L83-L119","documentation":"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.","triggerScenarios":"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.","commonSituations":"Running two Crush instances against the same DB; disk quota exhausted; DB file moved or corrupted mid-session; running on a read-only mount.","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"],"exampleFix":"// before\n// save fails with 'database is locked'\n// after\n// close the second crush instance (or set busy_timeout) and retry the todos update","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"failed to save todos\") {\n    if errors.Is(err, sqlite.ErrLocked) || isTransientIO(err) {\n        time.Sleep(backoff)\n        return retry()\n    }\n    return err\n}","preventionTips":["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"],"tags":["database","sqlite","persistence","write-failure"],"backgroundTag":"database-write-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}