feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(desktop): sign in through the browser, sharing the t3 connect credential - #7483

Open
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser
Open

feat(desktop): sign in through the browser, sharing the t3 connect credential#7483
t3dotgg wants to merge 9 commits into
mainfrom
t3code/move-electron-auth-to-browser

Conversation

@t3dotgg

@t3dotggt3dotgg commented Aug 19, 2026

Copy link
Copy Markdown
Member

Problem

Clerk inside Electron has been a constant source of auth bugs: passkey prompts firing on mount, hash-history redirect mangling, single-instance lock ordering, Linux AppImage scheme registration, and signed-build-only passkey testing on macOS.

Solution

The desktop app no longer runs any auth UI. Signing in opens the system browser through the bundled server, which runs the exact loopback PKCE flow npx t3 connect already uses and stores the same cloud-cli-oauth-token credential. Sign in from either the desktop app or the CLI and the other is signed in too.

  • Relay: the DPoP token-exchange endpoint now accepts OAuth tokens as subject_token through the same verifyRelayClientBearerToken verifier the REST endpoints already used.
  • Server: new /api/connect/auth/{state,login,logout,token} endpoints backed by CliTokenManager. beginBrowserLogin runs the loopback flow without a terminal. The stored credential gains an accountId (Clerk sub); legacy CLI credentials backfill it once via /oauth/userinfo.
  • Web: one useT3ConnectAuth() session hook, backed by Clerk on the web and by the local server under Electron. All shared components (relay session, link controller, onboarding, sidebar) consume the hook. Electron gets a small "finish in your browser" dialog and a plain account menu over the existing relay-backed pages.
  • Client runtime: DPoP token cache keys opaque OAuth tokens by their own value instead of bypassing the cache.
  • Desktop: @clerk/electron is gone — the bridge, preload, passkey autofill hack, redirect hack, Clerk/Turnstile CSP entries, Linux URL handler, OS-level t3code:// registration, and macOS passkey entitlements/provisioning requirements all deleted. A small DesktopSingleInstance service takes over the single-instance lock the Clerk bridge used to hold.

Net: −1900/+1200 lines, two dependencies removed, and the release workflow no longer needs APPLE_TEAM_ID, MACOS_PROVISIONING_PROFILE, or CLERK_PASSKEY_RP_DOMAINS.

The Catch

Won't have the full auth view in desktop app anymore. You get this instead
image

I think it's a worthwhile compromise.

Deploy order: the relay must ship before (or with) the desktop release. A new desktop app against an old relay gets invalid_bearer on the DPoP exchange, so every relay feature dies; an old app against the new relay is unaffected. After deploying, set CLERK_CLI_OAUTH_CLIENT_ID in the relay environment so OAuth bearer tokens are pinned to the CLI/desktop OAuth application.

Rollout note: a signed-out browser needs the hosted app to carry #6285 (preserve CLI OAuth parameters through sign-in, on main since Aug 12, not in the v0.0.33 stable deploy). The next stable release deploys the hosted app and ships this change together, so the ordering resolves itself; only source-build testing against today's prod hosted app needs T3CODE_HOSTED_APP_URL=https://nightly.app.t3.codes or an already signed-in browser.

No Clerk dashboard changes are required to ship this; the Native API/allowed_origins/AASA setup just becomes unused.

Screenshots of the new desktop sign-in dialog and account menu to follow after an integrated pass.


Built by Claude Fable 5 via Claude Code.


Note

High Risk
Large auth architecture change on desktop plus shared credential and new token endpoints; relay must deploy with OAuth DPoP support and optional client pinning before desktop users rely on connect features.

Overview
Desktop auth is no longer in-app Clerk. The Electron app removes @clerk/electron, preload bridge, Linux t3code:// URL-handler registration, Clerk CSP allowances, and macOS passkey provisioning/release requirements. DesktopSingleInstance takes the single-instance lock the Clerk bridge used to hold; startup deletes legacy clerk-tokens.json.

Sign-in runs on the bundled environment server using the same loopback PKCE flow as npx t3 connect, persisting one cloud-cli-oauth-token for desktop and CLI. CliTokenManager adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState (with accountId and userinfo backfill for older credentials). New /api/connect/auth/* handlers expose state, login, code, logout, and token to the renderer.

Web/Electron share one session API:useT3ConnectAuth with ClerkConnectAuthProvider (browser) vs DesktopConnectAuthProvider (polls the local server). Desktop gets a “finish in your browser” dialog, optional pasted OOB code, and a non-Clerk account menu; cloud link/onboarding/sidebar use the hook instead of Clerk directly.

Relay optionally pins OAuth bearer tokens via CLERK_CLI_OAUTH_CLIENT_ID; tests cover rejecting tokens from other OAuth apps.

Reviewed by Cursor Bugbot for commit 627b8cb. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Sign in to T3 Connect on desktop via the browser, sharing the CLI OAuth credential

  • Removes all Clerk Electron/passkey integration from the desktop app (dependencies, preload bridge, renderer provider, macOS signing/entitlements, protocol registration) and replaces it with a loopback PKCE browser sign-in flow handled by the bundled environment server.
  • Adds DesktopSingleInstance service to manage single-instance locking; secondary launches quit and surface the primary window.
  • Adds beginBrowserLogin, submitBrowserLoginCode, and clientAuthState to CloudCliTokenManager, including a loopback callback server with PKCE, timeout handling, and pending login state.
  • Exposes five new HTTP endpoints under /api/connect/auth/ (state, login, code, logout, token) in the environment server, consumed by both the desktop renderer and the web UI via a new useT3ConnectAuth context.
  • Opaque OAuth tokens (non-JWT) now participate in the DPoP credential cache in ManagedRelayClient, keyed by token value instead of bypassing the cache.
  • Adds optional CLERK_CLI_OAUTH_CLIENT_ID to the relay to restrict accepted OAuth bearer tokens to the designated OAuth app.
  • Risk: the desktop renderer no longer mounts Clerk at all; any feature that previously relied on Clerk in the Electron renderer will be broken.

Macroscope summarized 627b8cb.

@coderabbitai

coderabbitaiBot commented Aug 19, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b6e705c3-95b7-4edd-8e19-db93198b235c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 19, 2026

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding in the changed web UI: the new desktop account dialog is the only DialogPopup in apps/web/src without an accessible name. Everything else in the diff (Menu/Dialog/Button composition, sidebar avatar parity with the Clerk UserButton trigger classes, the sign-in prompt dialog) follows the existing primitive contracts.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx Outdated
Comment threadapps/server/src/cloud/http.ts
Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
Comment threadapps/web/src/components/clerk/useT3ConnectAuthPrompt.tsx
Comment threadapps/web/src/cloud/connectAuth.tsx
@github-actions

github-actionsBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire12.6 KiB12.6 KiB+8 B (+0.1%)15.1 KiB
CodexThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
CodexLive turn WebSocket wire6.3 KiB6.3 KiB+11 B (+0.2%)7.8 KiB
CodexLive turn WebSocket decoded51.8 KiB51.8 KiB0 B (0.0%)66.4 KiB
CodexLive turn messages16160 (0.0%)21
ClaudeTotal thread wire12.6 KiB12.6 KiB−24 B (−0.2%)15.1 KiB
ClaudeThread snapshot wire6.3 KiB6.3 KiB−3 B (−0.0%)7.3 KiB
ClaudeLive turn WebSocket wire6.3 KiB6.3 KiB−21 B (−0.3%)7.8 KiB
ClaudeLive turn WebSocket decoded52.7 KiB52.7 KiB0 B (0.0%)66.4 KiB
ClaudeLive turn messages16160 (0.0%)21

Baseline: 8bbbab5 · PR result: 1fab1f8 · Source CI: failure

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 99.9 KiB
  • Claude decoded thread snapshot: 100.6 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment threadapps/web/src/cloud/connectAuth.tsx Outdated
Comment threadapps/server/src/cloud/CliTokenManager.ts
@macroscopeapp

macroscopeappBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a major new authentication feature: replacing embedded Clerk desktop auth with browser-based OAuth shared with the CLI. It adds new credential endpoints, new auth providers, and new UI flows - significant architectural changes to security-sensitive authentication that warrant thorough review.

Notes:

  • No code objects were reviewed. Approvability was decided on eligibility alone.

No code changes detected at 627b8cb. Prior analysis still applies.

You can add or adjust custom eligibility rules. Learn more.

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One finding on the new desktop account control; the previously reported missing dialog accessible name in the same file is still open and is not re-posted here.

Posted via Macroscope — UI Consistency

Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/server/src/cloud/http.ts

@macroscopeappmacroscopeappBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the changed Effect service code (DesktopSingleInstance, CliTokenManager, connect HTTP handlers, managedRelay, contracts). Module/service structure, namespace imports, Context.Service + make + layer layout, and Effect.catchTags usage all follow the conventions, and the removed DesktopClerk/DesktopLinuxUrlHandler modules leave no re-export shims behind.

One finding: the single-instance behavior that moved out of DesktopClerk lost its test coverage even though the lock acquisition itself changed.

Posted via Macroscope — Effect Service Conventions

Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/http.ts Outdated
t3dotggand others added 7 commits August 19, 2026 01:28
…credential
Desktop drops @clerk/electron entirely. Sign-in opens the system browser
through the bundled server's loopback OAuth flow (the same one t3 connect
uses) and the stored credential is shared between the desktop app and the
CLI in both directions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the avatar menu crash (MenuGroupLabel outside MenuGroup) and adds an
out-of-band escape hatch: a browser that lands on the hosted code page can
paste the code into the waiting dialog, and the server exchanges it against
the hosted callback redirect URI for the same pending PKCE attempt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign-out cancels a pending browser login and fences its late persist
- login persists go through the credential semaphore
- a denied authorization fails the attempt promptly instead of waiting
out the callback timeout
- desktop auth state ignores out-of-order responses and drops the cached
access token when the shared credential switches accounts
- the waiting dialog shows a failure state with retry
- avatar trigger and account dialog get standard focus/cursor/aria
- CI preload check now asserts Clerk code is absent
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the behavioral cases from the deleted DesktopClerk suite: userData
ordering before lock acquisition, primary-instance second-instance handler
registration, and secondary-instance quit + bootstrap interrupt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- a fresh sign-in right after a sign-out no longer loses its pending
state to the cancelled attempt's cleanup, and waits for the previous
listener to release the loopback port
- HTTP-level scope tests for the connect auth endpoints
- relay optionally pins OAuth bearer tokens to the CLI OAuth client id
(CLERK_CLI_OAUTH_CLIENT_ID)
- stale @clerk/electron session store is removed on upgrade
- account menu says the sign-in is shared with the t3 CLI
- authToken exposure decision written down in code and docs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The URL is a capability to complete the pending sign-in with another
account; only relay:write sessions may see it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from d08ac69 to 9f4cb03CompareAugust 19, 2026 08:30
Comment threadapps/web/src/cloud/connectAuth.tsx
Comment threadapps/desktop/src/main.ts
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
Comment threadapps/server/src/cloud/CliTokenManager.ts Outdated
Comment threadapps/web/src/components/clerk/T3ConnectSidebarSignIn.tsx
Comment threadapps/desktop/src/app/DesktopSingleInstance.ts
- a second pasted code for the same attempt is reported as rejected
- the account dialog closes when the session signs out underneath it
- an in-flight token read cannot repopulate the cache after logout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment threadapps/server/src/cloud/CliTokenManager.test.ts
…gin tests
The tests share one real loopback origin; connection: close keeps the
global fetch dispatcher from reusing a socket the previous test's server
closed, and an afterEach cancels any still-pending attempt so a failure
cannot hold the port for the callback timeout.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the t3code/move-electron-auth-to-browser branch from 61b274f to 627b8cbCompareAugust 19, 2026 09:31

@cursorcursorBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

expiresAtEpochMs: token.value.expiresAtEpochMs,
accountId: token.value.accountId ?? null,
} satisfies EnvironmentConnectAuthToken;
}, Effect.catchTags(cloudCliTokenManagerErrorHandlers));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Signed-in UI with unusable tokens

Medium Severity

authState treats a stored credential as signed in even when refresh fails, while authToken uses getExisting and fails that same refresh. Desktop can keep showing a session and then getToken returns null, so every relay call dies until the user signs in again.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

// accountId can lag behind authorization for legacy `t3 connect`
// credentials while the server backfills it; relay features need the
// account id, so hold "signed in" until it resolves.
isSignedIn: state?.authorized === true && state.accountId !== null,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy sign-in backfill is not retried

Medium Severity

Desktop treats a user as signed in only when accountId is present, but polling stops as soon as the first authState response arrives. A legacy CLI credential that still needs the userinfo backfill can land as authorized with a null accountId and stay stuck on the sign-in button until a later focus refresh happens to succeed.

Additional Locations (1)
Fix in CursorFix in Web

Reviewed by Cursor Bugbot for commit 627b8cb. Configure here.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL1,000+ changed lines (additions + deletions).vouch:trustedPR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@t3dotgg