fix(error): replace misleading Database error suggestions

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 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-23 10:36:01 -05:00
parent 6e487532aa
commit f9e7913232

View File

@@ -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"],