Skip to content

fix(electron): handle Linux OAuth deep links - #9278

Merged
jeremy-clerk merged 6 commits into
mainfrom
jw/linux-electron-oauth-redirect
Jul 30, 2026
Merged

fix(electron): handle Linux OAuth deep links#9278
jeremy-clerk merged 6 commits into
mainfrom
jw/linux-electron-oauth-redirect

Conversation

@jeremy-clerk

@jeremy-clerk jeremy-clerk commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Description

Acquire Electron’s single-instance lock for OAuth deep links and forward secondary-instance callbacks to the primary process. Hosts that manage the process-wide lock can opt out with manageSingleInstanceLock: false.

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

@changeset-bot

changeset-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 26e0b8f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/electron Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview Jul 30, 2026 2:05am
swingset Ready Ready Preview Jul 30, 2026 2:05am

Request Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 29, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@9278

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@9278

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@9278

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@9278

@clerk/electron

npm i https://pkg.pr.new/@clerk/electron@9278

@clerk/electron-passkeys

npm i https://pkg.pr.new/@clerk/electron-passkeys@9278

@clerk/eslint-plugin

npm i https://pkg.pr.new/@clerk/eslint-plugin@9278

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@9278

@clerk/expo-google-signin

npm i https://pkg.pr.new/@clerk/expo-google-signin@9278

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@9278

@clerk/express

npm i https://pkg.pr.new/@clerk/express@9278

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@9278

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@9278

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@9278

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@9278

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@9278

@clerk/react

npm i https://pkg.pr.new/@clerk/react@9278

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@9278

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@9278

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@9278

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@9278

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@9278

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@9278

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@9278

commit: 26e0b8f

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

createClerkBridge now manages configurable Electron single-instance locking for renderer configurations and exposes isPrimaryInstance. OAuth transport matches callbacks from second-instance arguments and restores or focuses the initiating renderer window. Startup templates, documentation, tests, and the changeset reflect the updated behavior.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: wobsoriano

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly refers to the Linux OAuth deep-link fix in Electron.
Description check ✅ Passed The description matches the changeset by describing single-instance locking and forwarding OAuth callbacks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

@wobsoriano wobsoriano left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

thanks for the quick solution 🥇

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/electron/src/main/create-clerk-bridge.ts (1)

51-65: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Release the single-instance lock in finally. createClerkBridge() acquires it before later setup steps, and cleanup() only releases it on the normal path. If initialization or teardown throws, the bridge can leave the process lock behind. Wrap the setup/cleanup path in try/finally and add failure-path coverage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/electron/src/main/create-clerk-bridge.ts` around lines 51 - 65,
Update createClerkBridge() so ownership of the single-instance lock is released
in a finally block covering initialization and cleanup, including when setup or
teardown throws. Preserve the existing early return when
requestSingleInstanceLock() fails, and add coverage for lock release on failure
paths.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/electron/src/main/create-clerk-bridge.ts`:
- Around line 51-65: Update createClerkBridge() so ownership of the
single-instance lock is released in a finally block covering initialization and
cleanup, including when setup or teardown throws. Preserve the existing early
return when requestSingleInstanceLock() fails, and add coverage for lock release
on failure paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 01f3426d-2037-476f-9770-fdb5ee53cc1a

📥 Commits

Reviewing files that changed from the base of the PR and between ebd1e31 and dbfe0f4.

📒 Files selected for processing (5)
  • .changeset/calm-taxis-return.md
  • packages/electron/README.md
  • packages/electron/src/main/__tests__/create-clerk-bridge.test.ts
  • packages/electron/src/main/create-clerk-bridge.ts
  • packages/electron/src/shared/types.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/calm-taxis-return.md
  • packages/electron/README.md

Bring the initiating renderer window forward when a deep-link callback
resolves the pending flow. Windows and Linux hand the callback to a
background process, so the browser otherwise stays in front of the
signed-in window; macOS activates the app itself via open-url.

Warn when manageSingleInstanceLock is false and the application holds no
single-instance lock, since second-instance is the only delivery path on
Windows and Linux and cannot fire without it.

Release the single-instance lock when initialization or cleanup throws,
and make the release idempotent.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/electron/src/main/create-clerk-bridge.ts`:
- Around line 79-121: Update the bridge initialization flow around
setupTokenCacheIpcHandlers, setupPasskeysMain, and
setupOAuthTransportIpcHandlers to track each completed teardown as it becomes
available. In the catch path, invoke registered teardowns in reverse order,
including cleanup for partially initialized handlers when setup throws, then
release the single-instance lock and rethrow the original error. Preserve the
normal returned cleanup behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: ed8d4b4f-8df4-43c5-ad5d-e58807b28a7e

📥 Commits

Reviewing files that changed from the base of the PR and between dbfe0f4 and d050401.

📒 Files selected for processing (3)
  • packages/electron/src/main/__tests__/create-clerk-bridge.test.ts
  • packages/electron/src/main/create-clerk-bridge.ts
  • packages/electron/src/main/oauth-transport.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)

Comment thread packages/electron/src/main/create-clerk-bridge.ts
… macOS

Return isPrimaryInstance from createClerkBridge so applications can stop
their own bootstrap in a secondary process. app.quit() is asynchronous,
so whenReady can still fire in a process that is on its way out, loading
a window on a scheme that was never registered as privileged.

Stop acquiring the single-instance lock on macOS, where deep links reach
the running instance through open-url. The lock only buys callback
delivery where second-instance is the delivery path, so taking it on
macOS made apps single-instance for no functional gain.

Track teardowns on a stack and drain them in reverse when initialization
throws, so handlers registered before the failing step are removed
instead of leaking. Teardown errors during that drain are swallowed to
keep the original initialization error.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/electron/src/main/create-clerk-bridge.ts (1)

81-141: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

runTeardowns aborts on the first failing teardown, leaking the rest.

teardowns.pop()?.() isn't wrapped per-iteration, so if one teardown throws (e.g. ipcMain.removeHandler or the OAuth transport listener cleanup), the while loop exits and any remaining teardowns (registered earlier, e.g. token-cache/passkeys handlers) never run. In the success cleanup() path this also lets the exception escape uncaught since finally only guarantees the lock is released, not that the error is handled. In the init-failure catch path the outer catch {} only stops the teardown error from masking the original error — it doesn't resume cleaning up the rest of the stack.

🐛 Proposed fix: isolate each teardown call
   const teardowns: Array<() => void> = [];
   const runTeardowns = (): void => {
     while (teardowns.length > 0) {
-      teardowns.pop()?.();
+      try {
+        teardowns.pop()?.();
+      } catch (teardownErr) {
+        console.warn('Clerk: a teardown handler failed while cleaning up createClerkBridge resources.', teardownErr);
+      }
     }
   };

With this, the catch { /* swallow */ } wrapper around runTeardowns() in the init-failure branch (lines 133-137) becomes unnecessary but harmless to keep.

Consider also adding a regression test that registers multiple teardowns (e.g. passkeys: true + renderer) and makes one throw mid-stack, asserting the others still run.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/electron/src/main/create-clerk-bridge.ts` around lines 81 - 141,
Update runTeardowns to isolate each popped teardown invocation with its own
try/catch, swallowing individual teardown errors so the loop continues until
every registered cleanup runs. Preserve cleanup()’s lock release and the
initialization catch path’s original-error propagation, and add a regression
test covering multiple teardowns where one throws while the others still
execute.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/electron/src/main/create-clerk-bridge.ts`:
- Around line 81-141: Update runTeardowns to isolate each popped teardown
invocation with its own try/catch, swallowing individual teardown errors so the
loop continues until every registered cleanup runs. Preserve cleanup()’s lock
release and the initialization catch path’s original-error propagation, and add
a regression test covering multiple teardowns where one throws while the others
still execute.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: f8ff2eec-f61d-47e8-a066-c38d0202a75c

📥 Commits

Reviewing files that changed from the base of the PR and between d050401 and 140d9e6.

📒 Files selected for processing (6)
  • .changeset/calm-taxis-return.md
  • integration/templates/electron-vite/main.mjs
  • packages/electron/README.md
  • packages/electron/src/main/__tests__/create-clerk-bridge.test.ts
  • packages/electron/src/main/create-clerk-bridge.ts
  • packages/electron/src/shared/types.ts
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • clerk/clerk_go (manual)
  • clerk/dashboard (manual)
  • clerk/accounts (manual)
  • clerk/backoffice (manual)
  • clerk/clerk (manual)
  • clerk/clerk-docs (manual)
  • clerk/cloudflare-workers (manual)
  • clerk/clerk-ios (auto-detected)
  • clerk/clerk-android (auto-detected)
  • clerk/cli (auto-detected)
🚧 Files skipped from review as they are similar to previous changes (2)
  • .changeset/calm-taxis-return.md
  • packages/electron/README.md

Isolate each teardown in its own try/catch so the stack drains fully,
then rethrow the first error. Previously a throwing teardown abandoned
the rest: with passkeys and a renderer configured, the stack drains in
reverse, so a failure in the OAuth transport cleanup left the passkey
and token cache IPC handlers registered.

cleanup() still releases the lock and still surfaces the failure, and
the initialization catch path still propagates the original error.
@jeremy-clerk
jeremy-clerk merged commit 3c22061 into main Jul 30, 2026
50 checks passed
@jeremy-clerk
jeremy-clerk deleted the jw/linux-electron-oauth-redirect branch July 30, 2026 02:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants