From b10465c8838d492719dc1ac5854becac3d51e6b1 Mon Sep 17 00:00:00 2001 From: Peter Kirkham Date: Mon, 4 May 2026 12:23:11 +0100 Subject: [PATCH] feat(code): detect stale GitHub integration and offer Reconnect --- .../components/GitIntegrationStep.tsx | 84 ++++++++++++++----- .../src/renderer/hooks/useIntegrations.ts | 26 ++++-- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx index 36263396df..95bbd925a3 100644 --- a/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx +++ b/apps/code/src/renderer/features/onboarding/components/GitIntegrationStep.tsx @@ -110,8 +110,12 @@ export function GitIntegrationStep({ isLoading: githubUserIntegrationsLoading, } = useUserGithubIntegrations(); const hasGitIntegration = githubUserIntegrations.length > 0; - const { repositories, isLoadingRepos } = useUserRepositoryIntegration(); + const { repositories, isLoadingRepos, failedInstallationIds } = + useUserRepositoryIntegration(); const githubIntegration = githubUserIntegrations[0] ?? null; + const integrationIsStale = + !!githubIntegration && + failedInstallationIds.includes(githubIntegration.installation_id); const alternativeConnectedProjects = useMemo(() => { if (hasGitIntegration) return []; @@ -149,17 +153,18 @@ export function GitIntegrationStep({ const apiClient = useOptionalAuthenticatedClient(); const [disconnectConfirmOpen, setDisconnectConfirmOpen] = useState(false); const disconnectMutation = useMutation({ - mutationFn: async () => { + mutationFn: async (opts: { silent?: boolean } = {}) => { const installationId = githubIntegration?.installation_id; if (!apiClient || !installationId) { throw new Error("No GitHub integration to disconnect"); } await apiClient.disconnectGithubUserIntegration(installationId); + return { silent: opts.silent ?? false }; }, - onSuccess: () => { + onSuccess: ({ silent }) => { setDisconnectConfirmOpen(false); invalidateGithubQueries(queryClient, selectedProjectId); - toast.success("GitHub disconnected."); + if (!silent) toast.success("GitHub disconnected."); }, onError: (e) => { toast.error( @@ -339,28 +344,65 @@ export function GitIntegrationStep({ {isLoading || githubUserIntegrationsLoading ? ( ) : hasGitIntegration ? ( - - - - Connected + integrationIsStale ? ( + + Reconnect needed - + ) : ( + + + + Connected + + + ) ) : null} {hasGitIntegration ? ( - - {isLoadingRepos - ? "Loading repositories..." - : repoSummary - ? `Access to ${repoSummary}` - : "No repositories found. Check your GitHub app settings."} + + {integrationIsStale + ? "We can't reach GitHub with your saved authorization. The app may have been uninstalled or the token revoked. Reconnect to restore access." + : isLoadingRepos + ? "Loading repositories..." + : repoSummary + ? `Access to ${repoSummary}` + : "No repositories found. Check your GitHub app settings."} - + + {integrationIsStale && ( + + )}