diff --git a/internal/localdb/localdb.go b/internal/localdb/localdb.go index 0b47503..112c6a6 100644 --- a/internal/localdb/localdb.go +++ b/internal/localdb/localdb.go @@ -114,6 +114,13 @@ func New(dbPath string) (*LocalDB, error) { return nil, fmt.Errorf("opening sqlite database: %w", err) } + // SQLite requires a single writer connection to avoid SQLITE_BUSY under concurrent requests. + sqlDB, err := db.DB() + if err != nil { + return nil, fmt.Errorf("get sql.DB from gorm: %w", err) + } + sqlDB.SetMaxOpenConns(1) + // Enable WAL mode so background sync writes never block UI reads. if err := db.Exec("PRAGMA journal_mode=WAL").Error; err != nil { slog.Warn("failed to enable WAL mode", "error", err) @@ -121,6 +128,10 @@ func New(dbPath string) (*LocalDB, error) { if err := db.Exec("PRAGMA synchronous=NORMAL").Error; err != nil { slog.Warn("failed to set synchronous=NORMAL", "error", err) } + // Wait up to 5 s before returning SQLITE_BUSY (guards against WAL checkpoints and external locks). + if err := db.Exec("PRAGMA busy_timeout = 5000").Error; err != nil { + slog.Warn("failed to set busy_timeout", "error", err) + } if err := ensureLocalProjectsTable(db); err != nil { return nil, fmt.Errorf("ensure local_projects table: %w", err)