Problem
Two separate version-resolution gaps mean the Clerk code we ship is not the Clerk code we test or type-check against, and it can change without a commit.
1. The served SDK is not the installed SDK
package.json declares a range:
"@clerk/clerk-js": "^6.13.0"
vite.config.ts reads that string and strips the range prefix to build the Frontend API URL, injected as __CLERK_JS_VERSION__:
const CLERK_JS_VERSION = (CLERK_DEPS['@clerk/clerk-js'] || CLERK_DEV_DEPS['@clerk/clerk-js'] || '')
.replace(/^[\^~>=<\s]*/, '');
So production loads clerk.worldmonitor.app/npm/@clerk/clerk-js@6.13.0/dist/clerk.browser.js, while the lockfile installs 6.25.6 — which is what TypeScript compiles against. Twelve minor versions of drift between the runtime and its types. A type signature can describe an API the served bundle does not have, and npm run typecheck will pass.
2. The UI bundle floats across an entire major
src/services/clerk.ts:
const CLERK_UI_VERSION = '1';
@clerk/ui@1 resolves at request time. Clerk can change or remove parts of the prebuilt SignIn / UserProfile surfaces — including the passkey management section — with no repo change, no PR, and no CI signal. Anything we delegate to those prebuilt components is only as stable as Clerk's own major.
Why now
Surfaced while planning the passkey offer (docs/plans/2026-08-29-001-feat-clerk-passkey-post-login-offer-plan.md). That plan's verified API claims are anchored to the served 6.13.0 bundle rather than node_modules, precisely because of gap 1 — an earlier draft verified against 6.25.6 and was wrong about what ships.
Not caused by the passkey work, and not blocking it. Filed separately because it is auth infrastructure touching every user: a regression here must be revertable without reverting a feature.
Proposed change
- Pin
@clerk/clerk-js to an exact version in package.json (no ^), matching the lockfile so runtime and types agree.
- Pin
CLERK_UI_VERSION to an exact version rather than a bare major.
- Include
package-lock.json in the change.
- Add a guard test asserting the generated Frontend API URLs contain no range characters, and that the SDK version matches the installed one — so a future
^ reintroduction goes red.
Verification
npx tsx --test tests/clerk-runtime-version-pin.test.mts (new guard)
npm run typecheck
- Manual auth smoke on a preview deploy, recording the versions used: sign in, open the account profile, confirm an existing Pro entitlement still resolves. Unit tests cannot prove this; the blast radius is every signed-in user.
Landing
Its own commit and its own PR. Do not fold into a feature branch.
Acceptance criteria
Problem
Two separate version-resolution gaps mean the Clerk code we ship is not the Clerk code we test or type-check against, and it can change without a commit.
1. The served SDK is not the installed SDK
package.jsondeclares a range:vite.config.tsreads that string and strips the range prefix to build the Frontend API URL, injected as__CLERK_JS_VERSION__:So production loads
clerk.worldmonitor.app/npm/@clerk/clerk-js@6.13.0/dist/clerk.browser.js, while the lockfile installs 6.25.6 — which is what TypeScript compiles against. Twelve minor versions of drift between the runtime and its types. A type signature can describe an API the served bundle does not have, andnpm run typecheckwill pass.2. The UI bundle floats across an entire major
src/services/clerk.ts:@clerk/ui@1resolves at request time. Clerk can change or remove parts of the prebuiltSignIn/UserProfilesurfaces — including the passkey management section — with no repo change, no PR, and no CI signal. Anything we delegate to those prebuilt components is only as stable as Clerk's own major.Why now
Surfaced while planning the passkey offer (
docs/plans/2026-08-29-001-feat-clerk-passkey-post-login-offer-plan.md). That plan's verified API claims are anchored to the served 6.13.0 bundle rather thannode_modules, precisely because of gap 1 — an earlier draft verified against 6.25.6 and was wrong about what ships.Not caused by the passkey work, and not blocking it. Filed separately because it is auth infrastructure touching every user: a regression here must be revertable without reverting a feature.
Proposed change
@clerk/clerk-jsto an exact version inpackage.json(no^), matching the lockfile so runtime and types agree.CLERK_UI_VERSIONto an exact version rather than a bare major.package-lock.jsonin the change.^reintroduction goes red.Verification
npx tsx --test tests/clerk-runtime-version-pin.test.mts(new guard)npm run typecheckLanding
Its own commit and its own PR. Do not fold into a feature branch.
Acceptance criteria
@clerk/clerk-jspinned exactly; lockfile updatedCLERK_UI_VERSIONpinned exactly