From f9e79132327d15788f0c91c4b3630e3451f1bd83 Mon Sep 17 00:00:00 2001 From: teernisse Date: Mon, 23 Feb 2026 10:36:01 -0500 Subject: [PATCH] fix(error): replace misleading Database error suggestions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Database(rusqlite::Error) catch-all variant was suggesting 'lore reset --yes' for ALL database errors, including transient SQLITE_BUSY lock contention. This was wrong on two counts: 1. `lore reset` is not implemented (prints "not yet implemented") 2. Nuking the database is not the fix for a transient lock Changes: - Detect SQLITE_BUSY specifically via sqlite_error_code() and provide targeted advice: "Another process has the database locked" with common causes (cron sync, concurrent lore command) - Map SQLITE_BUSY to ErrorCode::DatabaseLocked (exit code 9) instead of DatabaseError (exit code 10) — semantically correct - Set BUSY actions to ["lore cron status"] (diagnostic) instead of the useless "lore sync --force" (--force overrides the app-level lock table, but SQLITE_BUSY fires before that table is even reached) - Fix MigrationFailed suggestion: also referenced non-existent 'lore reset', now says "try again" with lore migrate / lore doctor - Non-BUSY database errors get a simpler suggestion pointing to lore doctor (no more phantom reset command) Co-Authored-By: Claude Opus 4.6 --- src/core/error.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/core/error.rs b/src/core/error.rs index b9c5f2f..7d809fc 100644 --- a/src/core/error.rs +++ b/src/core/error.rs @@ -180,7 +180,13 @@ impl LoreError { Self::DatabaseLocked { .. } => ErrorCode::DatabaseLocked, Self::MigrationFailed { .. } => ErrorCode::MigrationFailed, Self::TokenNotSet { .. } => ErrorCode::TokenNotSet, - Self::Database(_) => ErrorCode::DatabaseError, + Self::Database(e) => { + if e.sqlite_error_code() == Some(rusqlite::ErrorCode::DatabaseBusy) { + ErrorCode::DatabaseLocked + } else { + ErrorCode::DatabaseError + } + } Self::Http(_) => ErrorCode::GitLabNetworkError, Self::Json(_) => ErrorCode::InternalError, Self::Io(_) => ErrorCode::IoError, @@ -218,14 +224,20 @@ impl LoreError { "Wait for other sync to complete or use --force.\n\n Example:\n lore ingest --force\n lore ingest issues --force", ), Self::MigrationFailed { .. } => Some( - "Check database file permissions or reset with 'lore reset'.\n\n Example:\n lore migrate\n lore reset --yes", + "Check database file permissions and try again.\n\n Example:\n lore migrate\n lore doctor", ), Self::TokenNotSet { .. } => Some( "Set your token:\n\n lore token set\n\n Or export to your shell:\n\n export GITLAB_TOKEN=glpat-xxxxxxxxxxxx\n\n Your token needs the read_api scope.", ), - Self::Database(_) => Some( - "Check database file permissions or reset with 'lore reset'.\n\n Example:\n lore doctor\n lore reset --yes", - ), + Self::Database(e) => { + if e.sqlite_error_code() == Some(rusqlite::ErrorCode::DatabaseBusy) { + Some( + "Another process has the database locked. Wait a moment and retry.\n\n Common causes:\n - A cron sync is running (lore cron status)\n - Another lore command is active", + ) + } else { + Some("Check database file permissions.\n\n Example:\n lore doctor") + } + } Self::Http(_) => Some("Check network connection"), Self::NotFound(_) => { Some("Verify the entity exists.\n\n Example:\n lore issues\n lore mrs") @@ -267,6 +279,11 @@ impl LoreError { Self::OllamaUnavailable { .. } => vec!["ollama serve"], Self::OllamaModelNotFound { .. } => vec!["ollama pull nomic-embed-text"], Self::DatabaseLocked { .. } => vec!["lore ingest --force"], + Self::Database(e) + if e.sqlite_error_code() == Some(rusqlite::ErrorCode::DatabaseBusy) => + { + vec!["lore cron status"] + } Self::EmbeddingsNotBuilt => vec!["lore embed"], Self::EmbeddingFailed { .. } => vec!["lore embed --retry-failed"], Self::MigrationFailed { .. } => vec!["lore migrate"],