Packages: @clerk/electron@0.0.24 (with @clerk/electron-passkeys@0.0.3), @clerk/clerk-js@6.25.13, @clerk/react@6.12.10, @clerk/shared@4.25.10
TL;DR: The Electron passkey provider reports passkey autofill as supported, but it executes the autofill request as a modal prompt. <SignIn> starts the autofill flow on mount, so an OS passkey dialog opens the instant the sign-in form renders, before any user intent.
Steps to reproduce:
- Electron app with
createClerkBridge({ passkeys: true, renderer: { scheme, host } }) in the main process and <ClerkProvider passkeys={passkeys}> from @clerk/electron/react + @clerk/electron/passkeys in the renderer (app served from a custom scheme).
- Enable passkeys with autofill on the Clerk instance.
- Sign in once and register a passkey.
- Sign out, then call
clerk.openSignIn() (or render <SignIn />).
Expected behavior:
Same as the browser: autofill is a background conditional-mediation request. Passkeys show up in the identifier field autocomplete. A passkey dialog appears only after the user picks one or clicks "Use passkey".
Actual behavior:
On macOS and Windows, the native OS passkey sheet opens immediately when the sign-in form mounts. On the renderer path (https-served Electron apps), Chromium's modal WebAuthn dialog opens instead. On Linux with a custom scheme, the flow fails silently with passkey_not_supported (no dialog, but the autofill attempt still errors).
Why (traced through the published dist of packages/electron):
- clerk-js's sign-in form checks
__internal_isWebAuthnAutofillSupported() and, if true, auto-starts SignIn.authenticateWithPasskey({ flow: 'autofill' }), passing conditionalUI: true to __internal_getPublicCredentials.
createPasskeys().isAutoFillSupported in the default auto mode returns isWebAuthnAutofillSupported() from @clerk/shared/webauthn, which is true in Electron's Chromium.
createPasskeys().get destructures only { publicKeyOptions } and drops conditionalUI:
- The renderer path hardcodes
webAuthnGetCredential({ publicKeyOptions, conditionalUI: false }), which @clerk/shared maps to mediation: "optional" — a modal request.
- A custom-scheme origin can never satisfy the RP ID in
originSatisfiesRpId, so decidePath routes to the native bridge on macOS/Windows — also modal.
So the provider advertises quiet autofill and then fulfills it with a blocking dialog.
Suggested fix:
Either honor conditionalUI in get() when the renderer path can do real conditional mediation, or make isAutoFillSupported() return false whenever the request would resolve to a modal path. It already returns false for mode: 'native'; the auto mode reports the renderer's capability even when the request will route native or be forced non-conditional.
Workaround we are shipping in T3 Code (pingdotgg/t3code#7437):
import { passkeys as electronPasskeys } from '@clerk/electron/passkeys';
const passkeys = {
...electronPasskeys,
isAutoFillSupported: () => Promise.resolve(false),
};
This stops the auto-prompt while keeping the explicit "Use passkey" button working.
🤖 Filed by Claude Code (Fable 5).
Packages:
@clerk/electron@0.0.24(with@clerk/electron-passkeys@0.0.3),@clerk/clerk-js@6.25.13,@clerk/react@6.12.10,@clerk/shared@4.25.10TL;DR: The Electron passkey provider reports passkey autofill as supported, but it executes the autofill request as a modal prompt.
<SignIn>starts the autofill flow on mount, so an OS passkey dialog opens the instant the sign-in form renders, before any user intent.Steps to reproduce:
createClerkBridge({ passkeys: true, renderer: { scheme, host } })in the main process and<ClerkProvider passkeys={passkeys}>from@clerk/electron/react+@clerk/electron/passkeysin the renderer (app served from a custom scheme).clerk.openSignIn()(or render<SignIn />).Expected behavior:
Same as the browser: autofill is a background conditional-mediation request. Passkeys show up in the identifier field autocomplete. A passkey dialog appears only after the user picks one or clicks "Use passkey".
Actual behavior:
On macOS and Windows, the native OS passkey sheet opens immediately when the sign-in form mounts. On the renderer path (https-served Electron apps), Chromium's modal WebAuthn dialog opens instead. On Linux with a custom scheme, the flow fails silently with
passkey_not_supported(no dialog, but the autofill attempt still errors).Why (traced through the published dist of
packages/electron):__internal_isWebAuthnAutofillSupported()and, if true, auto-startsSignIn.authenticateWithPasskey({ flow: 'autofill' }), passingconditionalUI: trueto__internal_getPublicCredentials.createPasskeys().isAutoFillSupportedin the defaultautomode returnsisWebAuthnAutofillSupported()from@clerk/shared/webauthn, which is true in Electron's Chromium.createPasskeys().getdestructures only{ publicKeyOptions }and dropsconditionalUI:webAuthnGetCredential({ publicKeyOptions, conditionalUI: false }), which@clerk/sharedmaps tomediation: "optional"— a modal request.originSatisfiesRpId, sodecidePathroutes to the native bridge on macOS/Windows — also modal.So the provider advertises quiet autofill and then fulfills it with a blocking dialog.
Suggested fix:
Either honor
conditionalUIinget()when the renderer path can do real conditional mediation, or makeisAutoFillSupported()returnfalsewhenever the request would resolve to a modal path. It already returnsfalseformode: 'native'; theautomode reports the renderer's capability even when the request will route native or be forced non-conditional.Workaround we are shipping in T3 Code (pingdotgg/t3code#7437):
This stops the auto-prompt while keeping the explicit "Use passkey" button working.
🤖 Filed by Claude Code (Fable 5).