Summary
On the Tauri desktop build, a Gemini API key entered via Settings → AI (components/ApiKeySection.tsx) is saved to the browser IndexedDB path (dbService → services/storage/idbKeyStore.ts), but services/geminiService.ts — the module that actually makes Gemini API calls — reads the key via storageService.getGeminiApiKey(), which on desktop resolves to the filesystem backend (services/fs/settingsFsStore.ts → config/gemini_key.enc.json).
These are two entirely separate storage locations. A key saved through the Settings UI on desktop is therefore never found by geminiService.ts.
Evidence
components/ApiKeySection.tsx: imports dbService directly (services/dbService.ts), calls dbService.hasGeminiApiKey() / getGeminiApiKey() / saveGeminiApiKey() / clearGeminiApiKey().
services/geminiService.ts: imports storageService (services/storageService.ts), calls storageService.getGeminiApiKey() / clearGeminiApiKey().
services/storageService.ts: constructor sets this.backend = fileSystemService when isTauriRuntime(), so on desktop getGeminiApiKey() routes to services/fs/settingsFsStore.ts, never to IndexedDB.
- Every other provider's key (OpenAI, OpenRouter, Claude, Grok, Ollama, etc.) is read/written exclusively through
storageService/aiProviderService.ts on both platforms, so they don't have this split — Gemini is the exception because ApiKeySection.tsx predates the multi-provider storageService abstraction and was never migrated onto it.
Impact
On the desktop (Tauri) build, saving a Gemini key via Settings → AI silently appears to succeed (the UI reports "Active"), but Gemini generation calls will fail to find the key. This affects the default/flagship AI provider specifically.
Suggested fix
Route ApiKeySection.tsx through storageService instead of dbService directly, matching the pattern every other provider already uses. Note this isn't a pure find-and-replace: dbService.getGeminiApiKey() returns a 'DECRYPT_FAILED' sentinel string that ApiKeySection.tsx's decryptFailed UI state depends on; services/fs/settingsFsStore.ts#getApiKey() has no equivalent sentinel today (it returns null on any unreadable-file case). Reconciling that semantic gap — or reworking the UI to not depend on the sentinel — needs to be part of the fix, along with regression tests covering both the web (IDB) and desktop (fs) paths.
Found via
Surfaced by chatgpt-codex-connector's review of PR #352 (docs(security): correct false desktop encryption claims), while validating the desktop BYOK-API-key documentation. Filed as a separate tracked bug since it's a functional/availability issue distinct from that PR's docs-only scope — see the README.md "Encryption — which mechanism protects what" table's Desktop BYOK API key row for a pointer back here.
Summary
On the Tauri desktop build, a Gemini API key entered via Settings → AI (
components/ApiKeySection.tsx) is saved to the browser IndexedDB path (dbService→services/storage/idbKeyStore.ts), butservices/geminiService.ts— the module that actually makes Gemini API calls — reads the key viastorageService.getGeminiApiKey(), which on desktop resolves to the filesystem backend (services/fs/settingsFsStore.ts→config/gemini_key.enc.json).These are two entirely separate storage locations. A key saved through the Settings UI on desktop is therefore never found by
geminiService.ts.Evidence
components/ApiKeySection.tsx: importsdbServicedirectly (services/dbService.ts), callsdbService.hasGeminiApiKey()/getGeminiApiKey()/saveGeminiApiKey()/clearGeminiApiKey().services/geminiService.ts: importsstorageService(services/storageService.ts), callsstorageService.getGeminiApiKey()/clearGeminiApiKey().services/storageService.ts: constructor setsthis.backend = fileSystemServicewhenisTauriRuntime(), so on desktopgetGeminiApiKey()routes toservices/fs/settingsFsStore.ts, never to IndexedDB.storageService/aiProviderService.tson both platforms, so they don't have this split — Gemini is the exception becauseApiKeySection.tsxpredates the multi-providerstorageServiceabstraction and was never migrated onto it.Impact
On the desktop (Tauri) build, saving a Gemini key via Settings → AI silently appears to succeed (the UI reports "Active"), but Gemini generation calls will fail to find the key. This affects the default/flagship AI provider specifically.
Suggested fix
Route
ApiKeySection.tsxthroughstorageServiceinstead ofdbServicedirectly, matching the pattern every other provider already uses. Note this isn't a pure find-and-replace:dbService.getGeminiApiKey()returns a'DECRYPT_FAILED'sentinel string thatApiKeySection.tsx'sdecryptFailedUI state depends on;services/fs/settingsFsStore.ts#getApiKey()has no equivalent sentinel today (it returnsnullon any unreadable-file case). Reconciling that semantic gap — or reworking the UI to not depend on the sentinel — needs to be part of the fix, along with regression tests covering both the web (IDB) and desktop (fs) paths.Found via
Surfaced by chatgpt-codex-connector's review of PR #352 (
docs(security): correct false desktop encryption claims), while validating the desktop BYOK-API-key documentation. Filed as a separate tracked bug since it's a functional/availability issue distinct from that PR's docs-only scope — see the README.md "Encryption — which mechanism protects what" table's Desktop BYOK API key row for a pointer back here.