diff --git a/apps/x/apps/main/src/oauth-handler.ts b/apps/x/apps/main/src/oauth-handler.ts index 483f25ee..3bb9063b 100644 --- a/apps/x/apps/main/src/oauth-handler.ts +++ b/apps/x/apps/main/src/oauth-handler.ts @@ -11,6 +11,7 @@ import { triggerSync as triggerGmailSync } from '@x/core/dist/knowledge/sync_gma import { triggerSync as triggerCalendarSync } from '@x/core/dist/knowledge/sync_calendar.js'; import { triggerSync as triggerFirefliesSync } from '@x/core/dist/knowledge/sync_fireflies.js'; import { emitOAuthEvent } from './ipc.js'; +import { getBillingInfo } from '@x/core/dist/billing/billing.js'; const REDIRECT_URI = 'http://localhost:8080/oauth/callback'; @@ -271,6 +272,17 @@ export async function connectProvider(provider: string, credentials?: { clientId triggerFirefliesSync(); } + // For Rowboat sign-in, ensure user + Stripe customer exist before + // notifying the renderer. Without this, parallel API calls from + // multiple renderer hooks race to create the user, causing duplicates. + if (provider === 'rowboat') { + try { + await getBillingInfo(); + } catch (meError) { + console.error('[OAuth] Failed to initialize user via /v1/me:', meError); + } + } + // Emit success event to renderer emitOAuthEvent({ provider, success: true }); } catch (error) { diff --git a/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts b/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts index a55b23fe..2f695f1a 100644 --- a/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts +++ b/apps/x/apps/renderer/src/components/onboarding/use-onboarding-state.ts @@ -535,6 +535,14 @@ export function useOnboardingState(open: boolean, onComplete: () => void) { const cleanup = window.ipc.on('oauth:didConnect', async (event) => { if (event.provider === 'rowboat' && event.success) { + // Ensure user record exists before advancing (prevents duplicate + // Stripe customers from parallel API calls in later steps) + try { + await window.ipc.invoke('billing:getInfo', null) + } catch (error) { + console.error('Failed to fetch billing info during onboarding:', error) + } + // Re-check composio flags now that the account is connected try { const [googleResult, calendarResult] = await Promise.all([