diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index abc516d..341d202 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3435,12 +3435,7 @@ fn hydrate_extension_pairing_token( app_state: tauri::State<'_, AppState>, ) -> Result { let mut connection = database.lock()?; - // Always skip the keychain during automatic hydration so the operating - // system never presents a credential-access prompt before the app's own - // modal is visible. The frontend will display the KeychainPermissionModal - // and only call grant_keychain_access — which touches the keychain — after - // the user explicitly clicks "Grant Access". - let skip_keychain = true; + let skip_keychain = !crate::db::is_keychain_access_granted(&connection).unwrap_or(false); match crate::db::hydrate_pairing_token(&mut connection, skip_keychain) { Ok((token, token_changed)) => { if let Ok(mut pairing_token) = app_state.extension_pairing_token.write() { diff --git a/src/components/KeychainPermissionModal.tsx b/src/components/KeychainPermissionModal.tsx index 6faebc8..a22822a 100644 --- a/src/components/KeychainPermissionModal.tsx +++ b/src/components/KeychainPermissionModal.tsx @@ -19,8 +19,15 @@ export const KeychainPermissionModal: React.FC = () => { try { const result = await invoke('grant_keychain_access'); if (result.persistent) { - useSettingsStore.setState({ keychainAccessGranted: true }); - await useSettingsStore.getState().hydratePairingToken(); + // Set all keychain-related state directly from the grant result + // instead of calling hydratePairingToken() again, which would + // re-read the DB before Zustand's persist middleware has written + // keychainAccessGranted and could flip persistent back to false. + useSettingsStore.setState({ + keychainAccessGranted: true, + extensionPairingToken: result.token, + isPairingTokenPersistent: true + }); setShowKeychainModal(false); } else { setError(result.error || 'Failed to grant keychain access.');