{"record":{"id":"b0028da557f5572f","repo":"paperclipai/paperclip","slug":"paperclip-couldn-t-preserve-this-sign-in-while-ref","errorCode":null,"errorMessage":"Paperclip couldn’t preserve this sign-in while refreshing your account.","messagePattern":"Paperclip couldn’t preserve this sign-in while refreshing your account\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/src/features/connections/ConnectionSetupFlow.tsx","lineNumber":707,"sourceCode":"      setOAuthError(\"The sign-in window closed. If authorization did not finish, try again.\");\n      setAuthorizationFallbackUrl(null);\n      onPhaseChange?.(\"needs_retry\");\n    }, 1_000);\n    return () => window.clearInterval(timer);\n  }, [host, oauthPhase, onPhaseChange]);\n\n  const prepareAndOpenOAuth = useCallback(async (\n    start: Pick<ToolOAuthStartResult, \"authorizationUrl\" | \"handoff\">,\n  ) => {\n    oauthHandoffAbortRef.current?.abort();\n    const controller = new AbortController();\n    oauthHandoffAbortRef.current = controller;\n    try {\n      const target = await prepareOAuthNavigation(start, { signal: controller.signal });\n      if (target.kind === \"reauthentication\") {\n        const destination = host === \"dialog\" ? oauthPopupRef.current : window;\n        if (!destination || destination.closed || !start.handoff) {\n          throw new Error(\"Paperclip couldn’t preserve this sign-in while refreshing your account.\");\n        }\n        savePendingCloudHandoff(start.handoff.session, destination.sessionStorage);\n        setOAuthPhase(\"starting\");\n      } else {\n        setAuthorizationHost(target.host);\n        setOAuthPhase(\"redirecting\");\n      }\n      openAuthorization(target.url);\n    } catch (error) {\n      if (controller.signal.aborted) return;\n      setOAuthPhase(\"error\");\n      setOAuthError(error instanceof Error ? error.message : \"Paperclip couldn’t start secure sign-in. Try again.\");\n      onPhaseChange?.(\"needs_retry\");\n    } finally {\n      if (oauthHandoffAbortRef.current === controller) oauthHandoffAbortRef.current = null;\n    }\n  }, [host, onPhaseChange, openAuthorization]);\n","sourceCodeStart":689,"sourceCodeEnd":725,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/ui/src/features/connections/ConnectionSetupFlow.tsx#L689-L725","documentation":"During OAuth reauthentication in ConnectionSetupFlow, after the cloud indicates a reauthentication flow, the code needs a destination (the dialog popup or the current window) plus a persisted handoff to carry session state. If the popup is missing, already closed, or the start result carries no handoff, the sign-in cannot be preserved across the account refresh, so the flow throws this user-facing error.","triggerScenarios":"Calling the OAuth start flow when the provider requires reauthentication and: `oauthPopupRef.current` is null or closed (host === \"dialog\"), `window` unavailable, or `start.handoff` is undefined.","commonSituations":"Browser blocked the popup so the ref is null; user closed the dialog before this step; server returned a reauthentication target but omitted the handoff payload; popup closed by user between steps.","solutions":["Allow popups for the Paperclip origin and restart the connection setup.","Retry the connection flow in the same tab (non-dialog host) if popups cannot be enabled.","Check the server response: if `handoff` is missing on a reauthentication start, fix the backend to return it."],"exampleFix":"// before\nconst destination = host === \"dialog\" ? oauthPopupRef.current : window;\nif (!destination || destination.closed || !start.handoff) throw new Error(\"...\");\n// after\nif (host === \"dialog\" && (!oauthPopupRef.current || oauthPopupRef.current.closed)) {\n  reopenDialogAndRetry(start); // re-open popup instead of failing\n  return;\n}\nconst destination = host === \"dialog\" ? oauthPopupRef.current : window;","handlingStrategy":"try-catch","validationCode":"function dialogReady(ref: React.RefObject<Window | null>): boolean {\n  return !!ref.current && !ref.current.closed;\n}","typeGuard":"function canPreserveSignIn(dest: Window | null, handoff: unknown): handoff is { session: string } {\n  return !!dest && !dest.closed && !!handoff && typeof handoff === \"object\" && \"session\" in handoff;\n}","tryCatchPattern":"try {\n  await beginOAuth(start, { host: \"dialog\" });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"preserve this sign-in\")) {\n    showPopupBlockedHelp(); // instruct user to allow popups, offer same-tab retry\n  } else throw e;\n}","preventionTips":["Detect popup blockers up front (window.open returns null) and warn before starting OAuth.","Reopen the dialog if `popup.closed` becomes true mid-flow instead of failing.","Assert the start response contains `handoff` for reauthentication targets in dev tests.","Keep a reference to the popup and listen for its close event to reset flow state."],"tags":["oauth","popup","reauthentication","ui"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}