gitbutlerapp/gitbutler · warning · Error

The browser did not finish signing in

Error message

The browser did not finish signing in

What it means

Thrown inside the Lite app's sign-in flow as the poll's per-attempt failure: after opening the browser at the login URL (with bt=release param), the code polls window.lite.getUserProfileLocal() and treats null as 'not signed in yet'. The message only becomes user-visible when pollUntilSuccess exhausts its timeout, meaning the but://login round trip never persisted an account.

Source

Thrown at apps/lite/ui/src/routes/project/$id/workspace/Settings/Account.tsx:59

		setSigningIn(true);
		setError(null);
		try {
			const login = await window.lite.getLoginToken();
			// Names the client for the login page, as apps/desktop does with its
			// build type. Without it the page can only offer a token to copy.
			const url = new URL(login.url);
			url.searchParams.set("bt", "release");
			await window.lite.openInWebBrowser(url.toString());
			// The page sends the account back over `but://login`, which the main
			// process persists, so this waits for the account to appear rather
			// than for a reply of its own.
			await pollUntilSuccess({
				attempt: async () => {
					// Signed out reads as `null` rather than an error, which would end
					// the poll on its first try. The message is only ever seen if the
					// poll then runs out of time, so it reads as the failure.
					const profile = await window.lite.getUserProfileLocal();
					if (profile === null) throw new Error("The browser did not finish signing in");
					return profile;
				},
				intervalMs: pollIntervalMs,
				timeoutMs: pollTimeoutMs,
				signal: controller.signal,
			});
			await client.invalidateQueries({ queryKey: userProfileQueryOptions.queryKey });
			await client.invalidateQueries({ queryKey: aiConfigurationQueryOptions.queryKey });
		} catch (caught) {
			// Abandoning is not a failure to report.
			if (!controller.signal.aborted) setError(errorMessageForToast(caught));
		} finally {
			if (!controller.signal.aborted) setSigningIn(false);
		}
	};

	return (
		<Section>

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Retry sign-in and complete it fully in the opened browser tab.
  2. Check the OS registers the but:// protocol with the Lite app (reinstall or repair protocol registration if not).
  3. Raise pollTimeoutMs for slower SSO flows so legitimate sign-ins are not cut off.
  4. Inspect main-process logs for the login deep-link handler to confirm the account was received and persisted.
Defensive patterns

Strategy: retry

Validate before calling

const profile = await window.lite.getUserProfileLocal();
if (profile === null) {
  setError("Not signed in yet — start sign-in from Account settings");
  return;
}

Try / catch

try {
  await pollUntilSuccess({ attempt, intervalMs: pollIntervalMs, timeoutMs: pollTimeoutMs, signal: controller.signal });
} catch (caught) {
  if (!controller.signal.aborted) {
    setError(caught instanceof Error && caught.message.includes("did not finish signing in")
      ? "Sign-in timed out — try again and complete it in the browser"
      : errorMessageForToast(caught));
  }
}

Prevention

When it happens

Trigger: User closed the browser tab without completing sign-in; the but:// deep link did not reach the main process (handler not registered / OS protocol association broken); the main process persisted the account but the local-profile read path keeps returning null.

Common situations: Default browser blocks custom protocol redirects; Lite app installed without but:// registration; slow SSO where sign-in takes longer than pollTimeoutMs; signed into a different account so the expected profile never appears.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/cf0afd097b0fed2c. Report an issue: GitHub.