Note for triage: filed at the request of Clerk support (Ryan) from an existing support ticket, where the root cause and evidence below were confirmed. This is an SDK hardening/observability request backed by a production incident, not a help request — hence no separate minimal-repro repo; deterministic repro steps are inline below.
Package: @clerk/backend (observed on 3.8.3; code is current on main)
Summary
When a nonce-format handshake's payload fetch fails, HandshakeService.getCookiesFromHandshake() (packages/backend/src/tokens/handshake.ts) catches the error, logs it with console.error, and resolves with zero cookie directives. resolveHandshake() then finds no session cookie and returns a plain signedOut(reason: AuthErrorReason.SessionTokenMissing, message: "").
The result: a server-side infrastructure failure (Backend API unreachable, misconfigured/missing secret key, transient 5xx) is laundered into an ordinary "signed out" auth state. The handshake "succeeds" empty, auth never converges, and nothing distinguishable surfaces to the adapter, middleware, or requestState consumer — the only trace is a console line in server logs.
// getCookiesFromHandshake()
if (this.authenticateContext.handshakeNonce) {
try {
const handshakePayload = await this.authenticateContext.apiClient?.clients.getHandshakePayload({
nonce: this.authenticateContext.handshakeNonce
});
if (handshakePayload) {
cookiesToSet.push(...handshakePayload.directives);
}
} catch (error) {
console.error('Clerk: HandshakeService: error getting handshake payload:', error);
}
}
Note the apiClient?. optional chaining has the same property: a missing apiClient silently yields no directives with no log at all.
Real-world impact
This made a production outage take days to diagnose. svelte-clerk constructed its Backend API client with an undefined secret key on Vercel (module-scope env read racing SvelteKit's Server.init() — fixed in wobsoriano/svelte-clerk#442). Every nonce handshake threw Missing Clerk Secret Key locally, before any request reached Clerk's API:
- Flows depending on server-side handshake convergence (Dashboard impersonation) dead-looped to the sign-in page with zero surfaced errors.
- Clerk support searched API logs for our handshake nonces and found nothing — because the SDK threw before the network call. That "zero requests" observation was only reconcilable once we found the swallowed
console.error in Vercel function logs.
- From the app's perspective the symptom was "auth randomly won't converge," which points at cookies, tokens, and clock skew — everywhere except the actual failure.
Context confirmed with Clerk support (Ryan) in an existing ticket; happy to link it internally.
Reproduction (deterministic, no impersonation needed)
- Any SvelteKit/Next.js/etc. app using
@clerk/backend's authenticateRequest on a production Clerk instance, deployed so that the Backend API call can fail — simplest forcing function: an invalid CLERK_SECRET_KEY at the point createClerkClient runs (that is exactly what the svelte-clerk bug produced).
- Send any request with a handshake nonce cookie:
curl https://your-app.example/ -H 'Cookie: __clerk_handshake_nonce=anything'.
- Observed: request resolves as generic signed-out (
reason: session-token-missing, empty message); the only evidence of the real failure is Clerk: HandshakeService: error getting handshake payload: … on the server console.
- Expected: the returned
requestState carries a distinguishable reason (and/or the SDK attempts a safe fallback) so the adapter/app can tell "infrastructure failure during handshake resolution" apart from "user is signed out."
Suggested hardening (either or both)
- Surface the failure distinctly. Return/propagate a dedicated auth error reason (e.g.
AuthErrorReason.HandshakePayloadFetchFailed) instead of the generic session-token-missing, so requestState consumers and framework adapters can log or act on it. Even keeping the signed-out outcome, a distinguishable reason/message would have turned a multi-day investigation into a five-minute log read.
- Fall back where safe. When the nonce payload fetch fails, fall back to a token-format handshake if the conditions allow, so a transient BAPI failure doesn't cost the user their session convergence.
Swallowing may be a deliberate availability choice (don't hard-fail requests on BAPI hiccups) — that's compatible with suggestion 1: keep resolving, but make the failure observable in the returned state, not only stderr.
Package:
@clerk/backend(observed on 3.8.3; code is current onmain)Summary
When a nonce-format handshake's payload fetch fails,
HandshakeService.getCookiesFromHandshake()(packages/backend/src/tokens/handshake.ts) catches the error, logs it withconsole.error, and resolves with zero cookie directives.resolveHandshake()then finds no session cookie and returns a plainsignedOut(reason: AuthErrorReason.SessionTokenMissing, message: "").The result: a server-side infrastructure failure (Backend API unreachable, misconfigured/missing secret key, transient 5xx) is laundered into an ordinary "signed out" auth state. The handshake "succeeds" empty, auth never converges, and nothing distinguishable surfaces to the adapter, middleware, or
requestStateconsumer — the only trace is a console line in server logs.Note the
apiClient?.optional chaining has the same property: a missingapiClientsilently yields no directives with no log at all.Real-world impact
This made a production outage take days to diagnose.
svelte-clerkconstructed its Backend API client with an undefined secret key on Vercel (module-scope env read racing SvelteKit'sServer.init()— fixed in wobsoriano/svelte-clerk#442). Every nonce handshake threwMissing Clerk Secret Keylocally, before any request reached Clerk's API:console.errorin Vercel function logs.Context confirmed with Clerk support (Ryan) in an existing ticket; happy to link it internally.
Reproduction (deterministic, no impersonation needed)
@clerk/backend'sauthenticateRequeston a production Clerk instance, deployed so that the Backend API call can fail — simplest forcing function: an invalidCLERK_SECRET_KEYat the pointcreateClerkClientruns (that is exactly what the svelte-clerk bug produced).curl https://your-app.example/ -H 'Cookie: __clerk_handshake_nonce=anything'.reason: session-token-missing, empty message); the only evidence of the real failure isClerk: HandshakeService: error getting handshake payload: …on the server console.requestStatecarries a distinguishable reason (and/or the SDK attempts a safe fallback) so the adapter/app can tell "infrastructure failure during handshake resolution" apart from "user is signed out."Suggested hardening (either or both)
AuthErrorReason.HandshakePayloadFetchFailed) instead of the genericsession-token-missing, sorequestStateconsumers and framework adapters can log or act on it. Even keeping the signed-out outcome, a distinguishablereason/messagewould have turned a multi-day investigation into a five-minute log read.Swallowing may be a deliberate availability choice (don't hard-fail requests on BAPI hiccups) — that's compatible with suggestion 1: keep resolving, but make the failure observable in the returned state, not only stderr.