Skip to content

feat(relay): Add managed relay tunnels and APN service#2837

Merged
juliusmarminge merged 66 commits into
mainfrom
codex/relay-managed-tunnels-auth-infra
Jun 5, 2026
Merged

feat(relay): Add managed relay tunnels and APN service#2837
juliusmarminge merged 66 commits into
mainfrom
codex/relay-managed-tunnels-auth-infra

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented May 28, 2026

Stack

Summary

This stacked draft PR adds the relay-managed tunnel and cloud authentication work on top of the mobile remote-runtime PR. General collection/performance rewrites from #2854 and the TypeScript/Effect tooling base are now on main.

  • add the relay worker/infrastructure package, persistence, APNs delivery, managed endpoint provisioning, observability, migrations, and tests
  • add standards-oriented relay authentication: DPoP proof handling, JWT/JWS signing and verification, OAuth-style token exchange/scopes, replay protections, and environment proof flows
  • add shared client-runtime/contracts/shared modules for managed relay operation across web and Expo mobile clients
  • add web, desktop, and mobile cloud linking and managed-environment flows, including mobile agent-awareness/live-activity registration
  • route relay-specific hashing and randomness through effect/Crypto while retaining Expo-compatible implementations

Validation

  • bun fmt
  • bun lint (passes with 8 existing web warnings)
  • bun lint:mobile
  • bun typecheck
  • cd infra/relay && bun run test (103 passed, 5 skipped)
  • cd apps/mobile && bun run test (135 passed)
  • cd apps/web && bun run test (1005 passed)
  • cd apps/server && bun run test (1075 passed, 4 skipped)

Rebase Note

General collection/performance rewrites from #2854 are now merged into main; mobile command metadata, pairing-URL redaction, and shared-runtime Crypto cleanup remain in #2013. This PR retains the managed-relay changes to the mobile connection contract and runtime above those inherited lower-layer changes.


Note

High Risk
Touches authentication (Clerk OAuth, protocol callbacks, token storage), new production relay deploy, and release pipeline env wiring; mobile raises minimum iOS to 18.0.

Overview
Adds T3 Cloud as an optional, config-gated product path: root .env.example documents public Clerk/relay settings, and CI gains a production relay deploy on main plus a release job that resolves relay URL and Clerk keys into desktop, CLI, and Vercel web builds.

Desktop gains end-to-end cloud sign-in: custom URL schemes (t3code / t3code-dev), macOS launcher/protocol registration for dev, DesktopCloudAuth (state-validated callbacks, single-instance routing), encrypted Clerk JWT storage, and IPC that proxies only allowed Clerk Frontend API hosts.

Mobile integrates Clerk (CloudAuthProvider), a Settings stack (environments, waitlist, T3 Cloud connect rows), agent push notification deep-linking, Live Activity preferences synced via relay when signed in, Expo widgets/notifications plugins, and iOS deployment target 18.0. Saved environments can record relayManaged metadata.

Docs and tooling shifts: README/AGENTS/mobile README describe optional cloud setup; desktop dev launcher and window navigation send off-origin OAuth to the system browser.

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

Note

Add managed relay tunnels with DPoP auth, APNs live activity delivery, and cloud CLI commands

  • Introduces a managed relay system where mobile and web clients connect to server environments via Cloudflare tunnels using DPoP-bound tokens; adds ManagedRelayClient, ManagedRelayDpopSigner, and platform-specific crypto/signer layers for mobile (Expo Crypto) and web (WebCrypto/IndexedDB)
  • Adds a relay Cloudflare Worker (infra/relay) with HTTP APIs for environment linking, credential issuance, agent awareness publishing, and APNs delivery of live activity updates and push notifications to mobile devices
  • Extends server auth (EnvironmentAuth, SessionStore, PairingGrantStore) to support DPoP-bound access tokens with replay prevention via a proof_key_thumbprint claim and per-request DPoP proof verification
  • Adds t3 cloud CLI commands (status, link, auth) with relay client install/management via a bundled cloudflared binary; the CLI is conditionally exposed based on build-time public config
  • Adds desktop Clerk integration: a fetch proxy routing Clerk Frontend API calls through the desktop bridge, OAuth sign-in flow with native callback handling, and encrypted JWT token storage
  • Adds mobile settings screens for cloud/waitlist enrollment, environments management, and agent awareness notification permissions including iOS Live Activities widget support
  • Deploys relay via a new GitHub Actions workflow on push to main; release pipeline now resolves and injects Clerk/relay public config into all build and deploy steps
  • Risk: DPoP proof verification adds a secret-store write (replay guard) on every authenticated request; consuming bootstrap credentials now requires a matching proofKeyThumbprint or fails, which is a breaking change for existing pairing flows that do not supply it

Macroscope summarized ee69e93.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

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

Run ID: 5ae0a773-2770-4d4a-982e-05b54e8f6bf5

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
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/relay-managed-tunnels-auth-infra

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

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels May 28, 2026
Comment thread apps/server/src/cloud/http.ts Outdated
Comment thread apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
Comment thread apps/mobile/src/app/settings/index.tsx
Comment thread packages/client-runtime/src/remote.ts Outdated
Comment thread apps/server/src/cloud/http.ts Outdated
Comment thread infra/relay/src/api.ts Outdated
Comment thread infra/relay/src/services/EnvironmentConnector.ts
Comment thread infra/relay/src/environments/EnvironmentCredentials.ts
Comment thread apps/server/src/cloud/ManagedEndpointRuntime.ts
Comment thread apps/desktop/src/app/DesktopCloudAuth.ts
Comment thread apps/mobile/src/features/agent-awareness/notificationNavigation.ts
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from 8480c92 to e3ab348 Compare May 28, 2026 08:13
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from a7ed828 to b868fee Compare May 28, 2026 08:16
Comment thread apps/mobile/src/features/agent-awareness/remoteRegistration.ts
Comment thread apps/mobile/src/features/cloud/linkEnvironment.ts Outdated
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from e3ab348 to 436b1b9 Compare May 28, 2026 16:38
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from b868fee to 589e2ed Compare May 28, 2026 16:38
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from 436b1b9 to d20a8ce Compare May 28, 2026 16:46
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch 2 times, most recently from 63a525d to 8027af0 Compare May 28, 2026 17:41
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch 2 times, most recently from 6c0e54d to f15e2ba Compare May 28, 2026 18:00
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from 8027af0 to 1a912f6 Compare May 28, 2026 18:00
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from f15e2ba to 71e0186 Compare May 28, 2026 18:14
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from 1a912f6 to 90bf2b3 Compare May 28, 2026 18:14
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from 71e0186 to e721336 Compare May 28, 2026 19:50
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch 2 times, most recently from e63e3f4 to ba9802d Compare May 28, 2026 20:26
Comment thread infra/relay/src/environments/EnvironmentCredentials.ts
Comment thread apps/server/src/auth/dpop.ts Outdated
Comment thread apps/web/src/cloud/desktopClerk.tsx Outdated
Comment thread apps/web/src/cloud/desktopClerk.tsx Outdated
Comment thread apps/web/src/cloud/desktopAuth.ts
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from 22e103a to 60b7d8d Compare May 28, 2026 21:01
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from ba9802d to 8789910 Compare May 28, 2026 21:02
Comment thread apps/mobile/src/features/agent-awareness/liveActivityController.ts Outdated
Comment thread apps/desktop/src/app/DesktopCloudAuthTokenStore.ts
Comment thread infra/relay/src/observability/Metrics.ts Outdated
@juliusmarminge juliusmarminge force-pushed the t3code/mobile-remote-connect branch from 60b7d8d to ee4ec05 Compare May 28, 2026 21:42
@juliusmarminge juliusmarminge force-pushed the codex/relay-managed-tunnels-auth-infra branch from 8789910 to f7ac694 Compare May 28, 2026 21:43
juliusmarminge and others added 20 commits June 4, 2026 14:03
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 and found 1 potential issue.

There are 6 total unresolved issues (including 5 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: OAuth callback ignored cold start
    • Added process.argv scanning in configure after event handler registration, using a new matchCloudAuthCallbackRoute helper (scheme/host/path only, no state validation) to detect and forward cold-start callback URLs directly to the renderer via IPC on Windows/Linux.

Create PR

Or push these changes by commenting:

@cursor push 4ee9127816
Preview (4ee9127816)
diff --git a/apps/desktop/src/app/DesktopCloudAuth.test.ts b/apps/desktop/src/app/DesktopCloudAuth.test.ts
--- a/apps/desktop/src/app/DesktopCloudAuth.test.ts
+++ b/apps/desktop/src/app/DesktopCloudAuth.test.ts
@@ -299,4 +299,34 @@
       }).pipe(Effect.provide(harness.layer), Effect.scoped);
     },
   );
+
+  it.effect("dispatches cold-start callback URL from process.argv on Windows/Linux", () => {
+    const callbackUrl =
+      "t3code://auth/callback?t3_state=prev-session-state&rotating_token_nonce=nonce-1";
+    const originalArgv = process.argv;
+    process.argv = ["electron", callbackUrl];
+    const harness = makeHarness({ isDevelopment: false });
+
+    return Effect.gen(function* () {
+      const cloudAuth = yield* DesktopCloudAuth.DesktopCloudAuth;
+      yield* cloudAuth.configure;
+      yield* flushCloudAuthDispatch;
+
+      assert.deepEqual(harness.sends, [
+        {
+          channel: IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
+          args: [callbackUrl],
+        },
+      ]);
+      assert.lengthOf(harness.reveals, 1);
+    }).pipe(
+      Effect.ensuring(
+        Effect.sync(() => {
+          process.argv = originalArgv;
+        }),
+      ),
+      Effect.provide(harness.layer),
+      Effect.scoped,
+    );
+  });
 });

diff --git a/apps/desktop/src/app/DesktopCloudAuth.ts b/apps/desktop/src/app/DesktopCloudAuth.ts
--- a/apps/desktop/src/app/DesktopCloudAuth.ts
+++ b/apps/desktop/src/app/DesktopCloudAuth.ts
@@ -66,10 +66,9 @@
   return url.toString();
 }
 
-export function parseCloudAuthCallbackUrl(input: {
+export function matchCloudAuthCallbackRoute(input: {
   readonly rawUrl: unknown;
   readonly scheme: string;
-  readonly state: string;
 }): URL | null {
   if (typeof input.rawUrl !== "string") {
     return null;
@@ -80,13 +79,23 @@
     if (url.protocol !== `${input.scheme}:`) return null;
     if (url.hostname !== CLOUD_AUTH_CALLBACK_HOST) return null;
     if (url.pathname !== CLOUD_AUTH_CALLBACK_PATHNAME) return null;
-    if (url.searchParams.get(CLOUD_AUTH_CALLBACK_STATE_PARAM) !== input.state) return null;
     return url;
   } catch {
     return null;
   }
 }
 
+export function parseCloudAuthCallbackUrl(input: {
+  readonly rawUrl: unknown;
+  readonly scheme: string;
+  readonly state: string;
+}): URL | null {
+  const url = matchCloudAuthCallbackRoute(input);
+  if (!url) return null;
+  if (url.searchParams.get(CLOUD_AUTH_CALLBACK_STATE_PARAM) !== input.state) return null;
+  return url;
+}
+
 export function findCloudAuthCallbackUrl(input: {
   readonly values: readonly unknown[];
   readonly scheme: string;
@@ -323,6 +332,28 @@
           );
         },
       );
+
+      // On Windows/Linux cold start, the protocol callback URL is delivered
+      // via process.argv rather than a second-instance or open-url event.
+      const coldStartValues = resolveProtocolClientLaunchArgs({ argv: process.argv });
+      for (const value of coldStartValues) {
+        const coldStartUrl = matchCloudAuthCallbackRoute({ rawUrl: value, scheme });
+        if (!coldStartUrl) continue;
+        pendingAuthRequest = closeCloudAuthRequest(pendingAuthRequest);
+        void runPromise(
+          Effect.gen(function* () {
+            yield* electronWindow.sendAll(
+              IpcChannels.CLOUD_AUTH_CALLBACK_CHANNEL,
+              coldStartUrl.toString(),
+            );
+            const mainWindow = yield* electronWindow.currentMainOrFirst;
+            if (Option.isSome(mainWindow)) {
+              yield* electronWindow.reveal(mainWindow.value);
+            }
+          }),
+        );
+        break;
+      }
     }).pipe(Effect.withSpan("desktop.cloudAuth.configure")),
   });
 });

You can send follow-ups to the cloud agent here.

Comment thread apps/desktop/src/app/DesktopCloudAuth.ts
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 and found 1 potential issue.

There are 6 total unresolved issues (including 5 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Duplicate instance continues startup
    • Replaced electronApp.quit (async graceful shutdown) with electronApp.exit(0) (immediate process termination) so the duplicate instance cannot proceed with backend bootstrap or window creation.

Create PR

Or push these changes by commenting:

@cursor push 1dbb6b6ec0
Preview (1dbb6b6ec0)
diff --git a/apps/desktop/src/app/DesktopCloudAuth.ts b/apps/desktop/src/app/DesktopCloudAuth.ts
--- a/apps/desktop/src/app/DesktopCloudAuth.ts
+++ b/apps/desktop/src/app/DesktopCloudAuth.ts
@@ -292,7 +292,7 @@
 
       const hasInstanceLock = yield* electronApp.requestSingleInstanceLock;
       if (!hasInstanceLock) {
-        return yield* electronApp.quit;
+        return yield* electronApp.exit(0);
       }
 
       yield* electronApp.on<[Electron.Event, string]>("open-url", (event, rawUrl) => {

You can send follow-ups to the cloud agent here.

const hasInstanceLock = yield* electronApp.requestSingleInstanceLock;
if (!hasInstanceLock) {
return yield* electronApp.quit;
}
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.

Duplicate instance continues startup

Medium Severity

When requestSingleInstanceLock is false, configure only calls app.quit() and returns. Startup in DesktopApp still proceeds to whenReady and backend bootstrap, so a duplicate desktop process can briefly run two servers or windows instead of exiting immediately.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 640dc63. Configure here.

Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 and found 1 potential issue.

There are 7 total unresolved issues (including 6 from previous reviews).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Disabled live activities still sync
    • Added setLocalLiveActivitiesEnabled export that is called when the user toggles the preference, and added initializeLiveActivityPreferenceState to hydrate the in-memory flag from stored preferences on app startup before environments connect.

Create PR

Or push these changes by commenting:

@cursor push a98d0e596a
Preview (a98d0e596a)
diff --git a/apps/mobile/src/features/agent-awareness/liveActivityController.ts b/apps/mobile/src/features/agent-awareness/liveActivityController.ts
--- a/apps/mobile/src/features/agent-awareness/liveActivityController.ts
+++ b/apps/mobile/src/features/agent-awareness/liveActivityController.ts
@@ -598,6 +598,10 @@
   return error instanceof Error && error.message.includes("Can't find live activity with id:");
 }
 
+export function setLocalLiveActivitiesEnabled(enabled: boolean): void {
+  localLiveActivitiesEnabled = enabled;
+}
+
 export function __resetAgentLiveActivitiesForTest(): void {
   localLiveActivitiesEnabled = true;
   activeActivity = null;

diff --git a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
--- a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
+++ b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.test.ts
@@ -8,7 +8,7 @@
 import type { SavedRemoteConnection } from "../../lib/connection";
 import { savePreferencesPatch } from "../../lib/storage";
 import { linkEnvironmentToCloud } from "../cloud/linkEnvironment";
-import { endAllAgentLiveActivities } from "./liveActivityController";
+import { endAllAgentLiveActivities, setLocalLiveActivitiesEnabled } from "./liveActivityController";
 import { setLiveActivityUpdatesEnabled } from "./liveActivityPreferences";
 import { refreshAgentAwarenessRegistration } from "./remoteRegistration";
 
@@ -22,6 +22,7 @@
 
 vi.mock("./liveActivityController", () => ({
   endAllAgentLiveActivities: vi.fn(() => Effect.void),
+  setLocalLiveActivitiesEnabled: vi.fn(),
 }));
 
 vi.mock("./remoteRegistration", () => ({

diff --git a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
--- a/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
+++ b/apps/mobile/src/features/agent-awareness/liveActivityPreferences.ts
@@ -3,11 +3,18 @@
 import { ManagedRelayClient } from "@t3tools/client-runtime";
 
 import type { SavedRemoteConnection } from "../../lib/connection";
-import { savePreferencesPatch } from "../../lib/storage";
+import { loadPreferences, savePreferencesPatch } from "../../lib/storage";
 import { linkEnvironmentToCloud } from "../cloud/linkEnvironment";
-import { endAllAgentLiveActivities } from "./liveActivityController";
+import { endAllAgentLiveActivities, setLocalLiveActivitiesEnabled } from "./liveActivityController";
 import { refreshAgentAwarenessRegistration } from "./remoteRegistration";
 
+export async function initializeLiveActivityPreferenceState(): Promise<void> {
+  const preferences = await loadPreferences();
+  if (preferences.liveActivitiesEnabled === false) {
+    setLocalLiveActivitiesEnabled(false);
+  }
+}
+
 export function setLiveActivityUpdatesEnabled(input: {
   readonly enabled: boolean;
   readonly clerkToken: string | null;
@@ -19,6 +26,8 @@
       catch: (error) => error,
     });
 
+    setLocalLiveActivitiesEnabled(input.enabled);
+
     if (!input.enabled) {
       yield* endAllAgentLiveActivities();
     }

diff --git a/apps/mobile/src/state/use-remote-environment-registry.ts b/apps/mobile/src/state/use-remote-environment-registry.ts
--- a/apps/mobile/src/state/use-remote-environment-registry.ts
+++ b/apps/mobile/src/state/use-remote-environment-registry.ts
@@ -59,6 +59,7 @@
   stopAgentAwarenessForEnvironment,
   stopAllAgentAwareness,
 } from "../features/agent-awareness/shellLiveActivitySync";
+import { initializeLiveActivityPreferenceState } from "../features/agent-awareness/liveActivityPreferences";
 import { environmentRuntimeManager, useEnvironmentRuntimeStates } from "./use-environment-runtime";
 import {
   clearCachedShellSnapshotMetadata,
@@ -565,6 +566,11 @@
           return;
         }
 
+        await initializeLiveActivityPreferenceState();
+        if (cancelled) {
+          return;
+        }
+
         replaceSavedConnections(
           Object.fromEntries(
             connections.map((connection) => [connection.environmentId, connection]),

You can send follow-ups to the cloud agent here.

Comment thread apps/mobile/src/features/agent-awareness/liveActivityController.ts Outdated
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

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 and found 1 potential issue.

There are 7 total unresolved issues (including 6 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Shared dev protocol scheme collision
    • Made APP_PROTOCOL_SCHEMES per-worktree using devBundleIdSuffix (matching APP_BUNDLE_ID), passed the scheme to Electron via T3CODE_DESKTOP_PROTOCOL_SCHEME env var, updated DesktopCloudAuth to use the override, and updated the launcher script URL pattern to use the dynamic scheme.

Create PR

Or push these changes by commenting:

@cursor push 4d22a1f898

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit ee69e93. Configure here.

"if (status !== 0) throw new Error(`LSSetDefaultHandlerForURLScheme failed: ${status}`);",
].join(" "),
]);
}
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.

Shared dev protocol scheme collision

Medium Severity

Development builds give each worktree a unique bundle id but still register the same t3code-dev URL scheme and call LSSetDefaultHandlerForURLScheme for it. macOS keeps one default handler per scheme, so the last dev launch steals cloud OAuth callbacks from other local worktrees.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee69e93. Configure here.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR 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