From 2aa28120cd29a8c164661c6f47cc25be67fa982e Mon Sep 17 00:00:00 2001 From: Mohamed Mahmoud Date: Mon, 11 May 2026 11:04:38 +0300 Subject: [PATCH] fix: protect providers map (#1268) ## What? Protected the `providers` map with a `sync.RWMutex` to fix a race condition. ## Why? `ensureProviders()` writes to the map on the Bubble Tea event loop while `getProvider()` is called concurrently from goroutines spawned by batch and search commands, causing unsafe concurrent map access. Closes #766 --- main.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 6e683d115a73e5d248497df511f96d8df7622297..e338f5233b2d27b1b5c0e12c1ea172d2a2e344a8 100644 --- a/main.go +++ b/main.go @@ -101,7 +101,8 @@ type mainModel struct { idleWatcher *fetcher.IdleWatcher idleUpdates chan fetcher.IdleUpdate // Multi-protocol backend providers (keyed by account ID) - providers map[string]backend.Provider + providers map[string]backend.Provider + providersMu sync.RWMutex // Daemon client service (daemon or direct fallback) service daemonclient.Service // Plugin prompt waiting for user input @@ -155,15 +156,23 @@ func (m *mainModel) ensureProviders() { return } for _, acct := range m.config.Accounts { - if _, ok := m.providers[acct.ID]; ok { + m.providersMu.RLock() + _, ok := m.providers[acct.ID] + m.providersMu.RUnlock() + + if ok { continue } + p, err := backend.New(&acct) if err != nil { log.Printf("backend: failed to create provider for %s: %v", acct.Email, err) continue } + + m.providersMu.Lock() m.providers[acct.ID] = p + m.providersMu.Unlock() } } @@ -172,7 +181,12 @@ func (m *mainModel) getProvider(acct *config.Account) backend.Provider { if acct == nil { return nil } - return m.providers[acct.ID] + + m.providersMu.RLock() + p := m.providers[acct.ID] + m.providersMu.RUnlock() + + return p } func (m *mainModel) Init() tea.Cmd {