fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(ui): revalidate test runs before advancing from the Test step - #9046

Merged
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue
Jul 2, 2026
Merged

fix(ui): revalidate test runs before advancing from the Test step#9046
iagodahlem merged 5 commits into
mainfrom
iago/orgs-1701-test-runs-on-continue

Conversation

@iagodahlem

@iagodahlemiagodahlem commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

What

On the Test step of the self-serve SSO configuration flow (<ConfigureSSO />), Continue gated on the locally-cached success probe. If a successful test run completed in a different browser tab, the local probe was stale, so Continue showed "requires a successful test run" and blocked until the user clicked Refresh logs. Continue now revalidates the success probe and gates on the fresh result, so a run completed elsewhere is recognized without a manual refresh.

If the local probe already shows success, Continue still advances immediately (no extra round-trip); the revalidate path only runs when the local state says "no success yet".

Notes

  • To gate Continue on the genuinely fresh probe value (rather than a stale closure / not-yet-updated ref), the internal __internal_useOrganizationEnterpriseConnectionTestRunsrevalidate now returns the freshly-fetched page (was void). Internal (__internal_) API; the only consumer is @clerk/ui.

Tests

Continue revalidates and advances when a successful run exists only server-side; a loading state on the button while the probe revalidates; the inline error when there is genuinely no successful run.

ORGS-1701

Summary by CodeRabbit

  • New Features

    • The SSO Test step now re-checks whether test runs succeeded when you click Continue, so success completed in another tab is recognized immediately.
    • The Continue button now reflects a loading/validating state while the success check is refreshed.
  • Bug Fixes

    • The flow advances only after the refreshed check confirms there is at least one successful test run.
    • If no successful run is found, the step shows the localized “no successful test run” error; if the check fails, the step displays the relevant error.

Continue on the Test step gated on a locally-cached success probe. A
test run completed in a different browser tab left that probe stale, so
Continue showed the "needs a successful test run" error and blocked
until the user clicked "Refresh logs" first.
Continue now revalidates the success probe and gates on the freshly
fetched result, advancing as soon as a run that succeeded elsewhere is
picked up. The button shows a loading state while revalidating and still
surfaces the inline error when there is genuinely no successful run.
@vercel

vercelBot commented Jun 30, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 30, 2026 8:27pm
swingsetReadyReadyPreview, CommentJun 30, 2026 8:27pm

Request Review

@coderabbitai

coderabbitaiBot commented Jun 30, 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

The shared test-run hook now returns fresh page data from revalidate. The UI exposes a probe revalidation boolean through organization connection state, and the Configure SSO test step uses it before advancing. Tests and the changeset were updated accordingly.

Changes

SSO test-run revalidation

Layer / File(s)Summary
Shared hook: revalidate returns data
packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx, packages/shared/src/react/hooks/index.ts, packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
Adds a typed revalidate result, supports exact invalidation, returns cached { data, totalCount }, and covers exact versus broad invalidation behavior in tests.
UI hook: probe revalidation boolean
packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts, packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
Adds revalidateHasSuccessfulTestRun, revalidates only the probe query, and verifies the probe path is used without calling the list revalidation.
Organization connection wiring
packages/ui/src/components/ConfigureSSO/hooks/useOrganizationEnterpriseConnection.ts
Extends TestRunsView and the memoized testRuns object to expose revalidateHasSuccessfulTestRun.
Continue button: async revalidation
packages/ui/src/components/ConfigureSSO/steps/TestConfigurationStep.tsx, packages/ui/src/components/ConfigureSSO/steps/__tests__/TestConfigurationStep.test.tsx
Passes the new revalidation function into the continue button, revalidates before advancing, and covers success, pending, and no-success cases in tests.
Changeset
.changeset/lucky-pears-doubt.md
Patch changeset for @clerk/shared and @clerk/ui documenting the SSO test-step revalidation behavior.

Sequence Diagram(s)

sequenceDiagram
participant User
participant ContinueTestSsoStepButton
participant useOrganizationEnterpriseConnection
participant useEnterpriseConnectionTestRuns
participant QueryClient
participant Wizard
User->>ContinueTestSsoStepButton: click Continue
ContinueTestSsoStepButton->>useOrganizationEnterpriseConnection: read testRuns.revalidateHasSuccessfulTestRun
useOrganizationEnterpriseConnection->>useEnterpriseConnectionTestRuns: call revalidateHasSuccessfulTestRun()
useEnterpriseConnectionTestRuns->>QueryClient: revalidateProbe({ armPolling: false, exact: true })
QueryClient-->>useEnterpriseConnectionTestRuns: fresh probe data
useEnterpriseConnectionTestRuns-->>ContinueTestSsoStepButton: boolean result
alt result is true
ContinueTestSsoStepButton->>Wizard: goNext()
else
ContinueTestSsoStepButton->>ContinueTestSsoStepButton: set no-success error
end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • wobsoriano

Poem

🐇 I hop to the gate and peek once more,
Fresh test-run sparkles slip through the door.
If success is new, I bounce ahead,
If not, I twitch my nose and stay instead.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly describes the main UI behavior change: revalidating test runs before advancing from the Test step.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@pkg-pr-new

pkg-pr-newBot commented Jun 30, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 584c4df

@iagodahlem
iagodahlem marked this pull request as ready for review June 30, 2026 18:00
@github-actions

github-actionsBot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-30T20:29:49.212Z

Summary

MetricCount
Packages analyzed19
Packages with changes1
🔴 Breaking changes25
🟡 Non-breaking changes1
🟢 Additions1

Warning
25 breaking change(s) detected - Major version bump required

🤖 This report was reviewed by claude-sonnet-4-6.

🔴 Breaking changes index (25)

Every breaking change, up front. Full diffs are in the package sections below.

PackageSubpathChange
@clerk/shared./internal/clerk-js/constantsERROR_CODES
@clerk/shared./reactUseOrganizationEnterpriseConnectionTestRunsReturn
@clerk/shared./typesBillingCreditBalanceJSON
@clerk/shared./typesBillingCreditBalanceJSON.balance
@clerk/shared./typesBillingCreditBalanceJSON.object
@clerk/shared./typesBillingCreditBalanceResource
@clerk/shared./typesBillingCreditBalanceResource.balance
@clerk/shared./typesBillingCreditLedgerJSON
@clerk/shared./typesBillingCreditLedgerJSON.amount
@clerk/shared./typesBillingCreditLedgerJSON.created_at
@clerk/shared./typesBillingCreditLedgerJSON.id
@clerk/shared./typesBillingCreditLedgerJSON.object
@clerk/shared./typesBillingCreditLedgerJSON.source_id
@clerk/shared./typesBillingCreditLedgerJSON.source_type
@clerk/shared./typesBillingCreditLedgerResource
@clerk/shared./typesBillingCreditLedgerResource.amount
@clerk/shared./typesBillingCreditLedgerResource.createdAt
@clerk/shared./typesBillingCreditLedgerResource.id
@clerk/shared./typesBillingCreditLedgerResource.sourceId
@clerk/shared./typesBillingCreditLedgerResource.sourceType
@clerk/shared./typesBillingNamespace.getCreditBalance
@clerk/shared./typesBillingNamespace.getCreditHistory
@clerk/shared./typesGetCreditBalanceParams
@clerk/shared./typesGetCreditHistoryParams
@clerk/shared./typesProfileSectionId

@clerk/shared

Current version: 4.22.1
Recommended bump: MAJOR → 5.0.0

Subpath ./internal/clerk-js/constants

🔴 Breaking Changes (1)

Changed: ERROR_CODES
// ... 9 unchanged lines elided ...
readonly SAML_USER_ATTRIBUTE_MISSING: "saml_user_attribute_missing";
readonly USER_LOCKED: "user_locked";
readonly EXTERNAL_ACCOUNT_NOT_FOUND: "external_account_not_found";
- readonly EXTERNAL_ACCOUNT_EXISTS: "external_account_exists";
readonly SESSION_EXISTS: "session_exists";
readonly SIGN_UP_MODE_RESTRICTED: "sign_up_mode_restricted";
readonly SIGN_UP_MODE_RESTRICTED_WAITLIST: "sign_up_restricted_waitlist";
// ... 13 unchanged lines elided ...

Static analyzer: Breaking change in variable ERROR_CODES: Type changed: {readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…{readonly FORM_IDENTIFIER_NOT_FOUND:"form_identifier_not_found";readonly FORM_PASSWORD_INCORRECT:"form_password_incorre…

🤖 AI review (confirmed) (95%): The EXTERNAL_ACCOUNT_EXISTS key ("external_account_exists") present in the baseline ERROR_CODES object was removed in the current version; consumers referencing ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS will fail to compile.

Migration: Replace any references to ERROR_CODES.EXTERNAL_ACCOUNT_EXISTS with the appropriate updated error code or handle the account-exists case via an alternative mechanism.

Subpath ./react

🔴 Breaking Changes (1)

Changed: UseOrganizationEnterpriseConnectionTestRunsReturn
// ... 4 unchanged lines elided ...
isLoading: boolean;
isFetching: boolean;
isPolling: boolean;
- revalidate: (options?: RevalidateTestRunsOptions) => Promise<void>;+ revalidate: (options?: RevalidateTestRunsOptions) => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>;
};

Static analyzer: Breaking change in type alias UseOrganizationEnterpriseConnectionTestRunsReturn: Type changed: {data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…{data:import("@clerk/shared").~EnterpriseConnectionTestRunResource[]|undefined;totalCount:number|undefined;error:!Error…

🤖 AI review (confirmed) (90%): The revalidate function previously returned Promise<void>; it now returns Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult>. Callers who await or chain .then() on the returned promise and type the result as void will still compile, but callers who explicitly typed the return as Promise<void> or passed the function reference to a () => Promise<void> slot will get a type error because the function's return type is now a wider, non-void type.

Migration: Update any code that references or assigns revalidate with an explicit () => Promise<void> type annotation to accept () => Promise<UseOrganizationEnterpriseConnectionTestRunsRevalidateResult> instead.

🟢 Additions (1)

Added: UseOrganizationEnterpriseConnectionTestRunsRevalidateResult
+ type UseOrganizationEnterpriseConnectionTestRunsRevalidateResult = {+ data: EnterpriseConnectionTestRunResource[] | undefined;+ totalCount: number | undefined;+ };

Added type alias UseOrganizationEnterpriseConnectionTestRunsRevalidateResult

Subpath ./types

🔴 Breaking Changes (23)

Changed: BillingCreditBalanceJSON
- interface BillingCreditBalanceJSON

Static analyzer: Removed interface BillingCreditBalanceJSON

🤖 AI review (confirmed) (95%): The BillingCreditBalanceJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceJSON from your code.

Changed: BillingCreditBalanceJSON.balance
- balance: BillingMoneyAmountJSON | null;

Static analyzer: Removed property BillingCreditBalanceJSON.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.balance from your code.

Changed: BillingCreditBalanceJSON.object
- object: 'commerce_credit_balance';

Static analyzer: Removed property BillingCreditBalanceJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditBalanceJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceJSON.object from your code.

Changed: BillingCreditBalanceResource
- interface BillingCreditBalanceResource

Static analyzer: Removed interface BillingCreditBalanceResource

🤖 AI review (confirmed) (95%): The BillingCreditBalanceResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditBalanceResource from your code.

Changed: BillingCreditBalanceResource.balance
- balance: BillingMoneyAmount | null;

Static analyzer: Removed property BillingCreditBalanceResource.balance

🤖 AI review (confirmed) (95%): The balance property of the removed BillingCreditBalanceResource interface is gone with the interface.

Migration: Remove all references to BillingCreditBalanceResource.balance from your code.

Changed: BillingCreditLedgerJSON
- interface BillingCreditLedgerJSON

Static analyzer: Removed interface BillingCreditLedgerJSON

🤖 AI review (confirmed) (95%): The BillingCreditLedgerJSON interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerJSON from your code.

Changed: BillingCreditLedgerJSON.amount
- amount: BillingMoneyAmountJSON;

Static analyzer: Removed property BillingCreditLedgerJSON.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.amount from your code.

Changed: BillingCreditLedgerJSON.created_at
- created_at: number;

Static analyzer: Removed property BillingCreditLedgerJSON.created_at

🤖 AI review (confirmed) (95%): The created_at property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.created_at from your code.

Changed: BillingCreditLedgerJSON.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.id from your code.

Changed: BillingCreditLedgerJSON.object
- object: 'commerce_credit_ledger';

Static analyzer: Removed property BillingCreditLedgerJSON.object

🤖 AI review (confirmed) (95%): The object property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.object from your code.

Changed: BillingCreditLedgerJSON.source_id
- source_id: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_id

🤖 AI review (confirmed) (95%): The source_id property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_id from your code.

Changed: BillingCreditLedgerJSON.source_type
- source_type: string;

Static analyzer: Removed property BillingCreditLedgerJSON.source_type

🤖 AI review (confirmed) (95%): The source_type property of the removed BillingCreditLedgerJSON interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerJSON.source_type from your code.

Changed: BillingCreditLedgerResource
- interface BillingCreditLedgerResource

Static analyzer: Removed interface BillingCreditLedgerResource

🤖 AI review (confirmed) (95%): The BillingCreditLedgerResource interface is removed entirely, breaking any consumer code that imports or references it.

Migration: Remove all references to BillingCreditLedgerResource from your code.

Changed: BillingCreditLedgerResource.amount
- amount: BillingMoneyAmount;

Static analyzer: Removed property BillingCreditLedgerResource.amount

🤖 AI review (confirmed) (95%): The amount property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.amount from your code.

Changed: BillingCreditLedgerResource.createdAt
- createdAt: Date;

Static analyzer: Removed property BillingCreditLedgerResource.createdAt

🤖 AI review (confirmed) (95%): The createdAt property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.createdAt from your code.

Changed: BillingCreditLedgerResource.id
- id: string;

Static analyzer: Removed property BillingCreditLedgerResource.id

🤖 AI review (confirmed) (95%): The id property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.id from your code.

Changed: BillingCreditLedgerResource.sourceId
- sourceId: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceId

🤖 AI review (confirmed) (95%): The sourceId property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceId from your code.

Changed: BillingCreditLedgerResource.sourceType
- sourceType: string;

Static analyzer: Removed property BillingCreditLedgerResource.sourceType

🤖 AI review (confirmed) (95%): The sourceType property of the removed BillingCreditLedgerResource interface is gone with the interface.

Migration: Remove all references to BillingCreditLedgerResource.sourceType from your code.

Changed: BillingNamespace.getCreditBalance
- getCreditBalance: (params: GetCreditBalanceParams) => Promise<BillingCreditBalanceResource>;

Static analyzer: Removed property BillingNamespace.getCreditBalance

🤖 AI review (confirmed) (95%): The getCreditBalance method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditBalance or find an alternative API for retrieving credit balance.

Changed: BillingNamespace.getCreditHistory
- getCreditHistory: (params: GetCreditHistoryParams) => Promise<ClerkPaginatedResponse<BillingCreditLedgerResource>>;

Static analyzer: Removed property BillingNamespace.getCreditHistory

🤖 AI review (confirmed) (95%): The getCreditHistory method is removed from BillingNamespace, breaking any consumer that calls it.

Migration: Remove calls to getCreditHistory or find an alternative API for retrieving credit history.

Changed: GetCreditBalanceParams
- type GetCreditBalanceParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditBalanceParams

🤖 AI review (confirmed) (95%): The GetCreditBalanceParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditBalanceParams from your code.

Changed: GetCreditHistoryParams
- type GetCreditHistoryParams = {- orgId?: string;- };

Static analyzer: Removed type alias GetCreditHistoryParams

🤖 AI review (confirmed) (95%): The GetCreditHistoryParams type alias is removed, breaking any consumer code that imports or uses it.

Migration: Remove all imports and references to GetCreditHistoryParams from your code.

Changed: ProfileSectionId
- type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults' | 'accountCredits';+ type ProfileSectionId = 'profile' | 'username' | 'emailAddresses' | 'phoneNumbers' | 'connectedAccounts' | 'enterpriseAccounts' | 'web3Wallets' | 'password' | 'passkeys' | 'mfa' | 'danger' | 'activeDevices' | 'organizationProfile' | 'organizationDanger' | 'organizationDomains' | 'manageVerifiedDomains' | 'subscriptionsList' | 'paymentMethods' | 'sso' | 'ssoStatus' | 'enableSso' | 'ssoDomain' | 'ssoConfiguration' | 'configureAgain' | 'resetSso' | 'testSsoUrl' | 'testResults';

Static analyzer: Breaking change in type alias ProfileSectionId: Type changed: 'accountCredits'|'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterprise…'activeDevices'|'configureAgain'|'connectedAccounts'|'danger'|'emailAddresses'|'enableSso'|'enterpriseAccounts'|'manage…

🤖 AI review (confirmed) (95%): The 'accountCredits' variant was removed from the ProfileSectionId union, and ProfileSectionId is used as part of MenuId, so any consumer passing 'accountCredits' as a ProfileSectionId or MenuId will get a type error.

Migration: Remove any usage of 'accountCredits' as a ProfileSectionId or MenuId value in your code.

🟡 Non-breaking Changes (1)

Modified: __internal_LocalizationResource
Diff (before: 1965 lines, after: 1947 lines). Click to expand.
// ... 845 unchanged lines elided ...
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
headerTitle__subscriptions: LocalizationValue;
headerTitle__statements: LocalizationValue;
};
statementsSection: {
empty: LocalizationValue;
itemCaption__paidForPlan: LocalizationValue;
itemCaption__proratedCredit: LocalizationValue;
itemCaption__payerCredit: LocalizationValue;
itemCaption__subscribedAndPaidForPlan: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
title: LocalizationValue;
totalPaid: LocalizationValue;
};
switchPlansSection: {
title: LocalizationValue;
};
subscriptionsListSection: {
tableHeader__plan: LocalizationValue;
tableHeader__startDate: LocalizationValue;
tableHeader__edit: LocalizationValue;
title: LocalizationValue;
actionLabel__newSubscription: LocalizationValue;
actionLabel__manageSubscription: LocalizationValue;
actionLabel__switchPlan: LocalizationValue;
overview: LocalizationValue;
};
paymentHistorySection: {
empty: LocalizationValue;
notFound: LocalizationValue;
tableHeader__date: LocalizationValue;
tableHeader__amount: LocalizationValue;
tableHeader__status: LocalizationValue;
};
paymentMethodsSection: {
title: LocalizationValue;
add: LocalizationValue;
addSubtitle: LocalizationValue;
cancelButton: LocalizationValue;
actionLabel__default: LocalizationValue;
actionLabel__remove: LocalizationValue;
formButtonPrimary__add: LocalizationValue;
formButtonPrimary__pay: LocalizationValue;
removeMethod: {
title: LocalizationValue;
messageLine1: LocalizationValue<'identifier'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue<'paymentMethod'>;
};
payWithTestCardButton: LocalizationValue;
};
subscriptionsSection: {
actionLabel__default: LocalizationValue;
};
};
plansPage: {
title: LocalizationValue;
alerts: {
noPermissionsToManageBilling: LocalizationValue;
};
};
};
userButton: {
action__manageAccount: LocalizationValue;
action__signOut: LocalizationValue;
action__signOutAll: LocalizationValue;
action__addAccount: LocalizationValue;
action__openUserMenu: LocalizationValue;
action__closeUserMenu: LocalizationValue;
label__userButtonPopover?: LocalizationValue;
label__accountActions?: LocalizationValue;
label__activeSessions?: LocalizationValue;
};
organizationSwitcher: {
personalWorkspace: LocalizationValue;
notSelected: LocalizationValue;
action__createOrganization: LocalizationValue;
action__manageOrganization: LocalizationValue;
action__invitationAccept: LocalizationValue;
action__suggestionsAccept: LocalizationValue;
action__openOrganizationSwitcher: LocalizationValue;
action__closeOrganizationSwitcher: LocalizationValue;
suggestionsAcceptedLabel: LocalizationValue;
};
impersonationFab: {
title: LocalizationValue<'identifier'>;
action__signOut: LocalizationValue;
};
organizationProfile: {
navbar: {
title: LocalizationValue;
description: LocalizationValue;
general: LocalizationValue;
members: LocalizationValue;
billing: LocalizationValue;
apiKeys: LocalizationValue;
security: LocalizationValue;
};
badge__unverified: LocalizationValue;
badge__automaticInvitation: LocalizationValue;
badge__automaticSuggestion: LocalizationValue;
badge__manualInvitation: LocalizationValue;
badge__enterpriseSso: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
membershipSeatUsageLabel: LocalizationValue<'count' | 'limit'>;
headerTitle__general: LocalizationValue;
profileSection: {
title: LocalizationValue;
primaryButton: LocalizationValue;
uploadAction__title: LocalizationValue;
};
};
profilePage: {
title: LocalizationValue;
successMessage: LocalizationValue;
dangerSection: {
title: LocalizationValue;
leaveOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
};
deleteOrganization: {
title: LocalizationValue;
messageLine1: LocalizationValue;
messageLine2: LocalizationValue;
actionDescription: LocalizationValue<'organizationName'>;
successMessage: LocalizationValue;
};
};
domainSection: {
title: LocalizationValue;
subtitle: LocalizationValue;
primaryButton: LocalizationValue;
menuAction__verify: LocalizationValue;
menuAction__remove: LocalizationValue;
menuAction__manage: LocalizationValue;
};
};
createDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
verifyDomainPage: {
title: LocalizationValue;
subtitle: LocalizationValue<'domainName'>;
subtitleVerificationCodeScreen: LocalizationValue<'emailAddress'>;
formTitle: LocalizationValue;
formSubtitle: LocalizationValue;
resendButton: LocalizationValue;
};
verifiedDomainPage: {
title: LocalizationValue<'domain'>;
subtitle: LocalizationValue<'domain'>;
start: {
headerTitle__enrollment: LocalizationValue;
headerTitle__danger: LocalizationValue;
};
enrollmentTab: {
subtitle: LocalizationValue;
manualInvitationOption__label: LocalizationValue;
manualInvitationOption__description: LocalizationValue;
automaticInvitationOption__label: LocalizationValue;
automaticInvitationOption__description: LocalizationValue;
automaticSuggestionOption__label: LocalizationValue;
automaticSuggestionOption__description: LocalizationValue;
calloutInfoLabel: LocalizationValue;
calloutInvitationCountLabel: LocalizationValue<'count'>;
calloutSuggestionCountLabel: LocalizationValue<'count'>;
};
dangerTab: {
removeDomainTitle: LocalizationValue;
removeDomainSubtitle: LocalizationValue;
removeDomainActionLabel__remove: LocalizationValue;
calloutInfoLabel: LocalizationValue;
};
};
invitePage: {
title: LocalizationValue;
subtitle: LocalizationValue;
successMessage: LocalizationValue;
detailsTitle__inviteFailed: LocalizationValue<'email_addresses'>;
formButtonPrimary__continue: LocalizationValue;
formButtonPrimary__purchaseSeats: LocalizationValue;
selectDropdown__role: LocalizationValue;
};
removeDomainPage: {
title: LocalizationValue;
messageLine1: LocalizationValue<'domain'>;
messageLine2: LocalizationValue;
successMessage: LocalizationValue;
};
securityPage: {
title: LocalizationValue;
removeDialog: {
title: LocalizationValue;
subtitle: LocalizationValue;
confirmButton: LocalizationValue;
};
ssoSection: {
title: LocalizationValue;
badge__unconfigured: LocalizationValue;
badge__inProgress: LocalizationValue;
badge__active: LocalizationValue;
badge__inactive: LocalizationValue;
descriptionLine1: LocalizationValue;
primaryButton__startConfiguration: LocalizationValue;
primaryButton__continueConfiguration: LocalizationValue;
domainLabel: LocalizationValue;
menuAction__edit: LocalizationValue;
menuAction__activate: LocalizationValue;
menuAction__deactivate: LocalizationValue;
menuAction__remove: LocalizationValue;
tooltip: LocalizationValue<'role'>;
tooltip__noRole: LocalizationValue;
tooltipLabel: LocalizationValue;
};
};
membersPage: {
detailsTitle__emptyRow: LocalizationValue;
action__invite: LocalizationValue;
action__search: LocalizationValue;
start: {
headerTitle__members: LocalizationValue;
headerTitle__invitations: LocalizationValue;
headerTitle__requests: LocalizationValue;
};
activeMembersTab: {
tableHeader__user: LocalizationValue;
tableHeader__joined: LocalizationValue;
tableHeader__role: LocalizationValue;
tableHeader__actions: LocalizationValue;
menuAction__remove: LocalizationValue;
};
invitedMembersTab: {
tableHeader__invited: LocalizationValue;
menuAction__revoke: LocalizationValue;
};
invitationsTab: {
table__emptyRow: LocalizationValue;
autoInvitations: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
requestsTab: {
tableHeader__requested: LocalizationValue;
menuAction__approve: LocalizationValue;
menuAction__reject: LocalizationValue;
table__emptyRow: LocalizationValue;
autoSuggestions: {
headerTitle: LocalizationValue;
headerSubtitle: LocalizationValue;
primaryButton: LocalizationValue;
};
};
alerts: {
roleSetMigrationInProgress: {
title: LocalizationValue;
subtitle: LocalizationValue;
};
};
};
billingPage: {
title: LocalizationValue;
- accountCreditsSection: {- title: LocalizationValue;- viewHistory: LocalizationValue;- };- creditHistoryPage: {- title: LocalizationValue;- tableHeader__amount: LocalizationValue;- tableHeader__date: LocalizationValue;- };
start: {
headerTitle__payments: LocalizationValue;
headerTitle__plans: LocalizationValue;
// ... 823 unchanged lines elided ...

Static analyzer: Breaking change in type alias __internal_LocalizationResource: Type changed: {locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…{locale:string;maintenanceMode:import("@clerk/shared").LocalizationValue;roles:{[r:string]:import("@clerk/shared").Loca…

🤖 AI review (reclassified as non-breaking) (60%): The before and after snippets are nearly identical large objects; the only visible difference is '1885 lines elided' vs '1867 lines elided', suggesting some fields were removed. However, __internal_LocalizationResource is used only as the base of DeepPartial<DeepLocalizationWithoutObjects<__internal_LocalizationResource>> for LocalizationResource, meaning consumers only read/extend from it rather than constructing exact values of this type. Without being able to confirm which specific fields were removed, but given the internal/output nature, confidence is low that this breaks consumers.


Report generated by Break Check

Last ran on 584c4df.

@iagodahlem
iagodahlemforce-pushed the iago/orgs-1701-test-runs-on-continue branch from a31554a to da3f2fcCompareJune 30, 2026 18:17
@changeset-bot

changeset-botBot commented Jun 30, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 584c4df

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

This PR includes changesets to release 23 packages
NameType
@clerk/sharedPatch
@clerk/uiPatch
@clerk/astroPatch
@clerk/backendPatch
@clerk/chrome-extensionPatch
@clerk/clerk-jsPatch
@clerk/electronPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/headlessPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nextjsPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/vuePatch
@clerk/swingsetPatch

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

@iagodahlemiagodahlem changed the title fix(ui): revalidate test runs on Continue and stop mid-flow refetches gating the security pagefix(ui): revalidate test runs before advancing from the Test stepJun 30, 2026
The shared test-runs hook's revalidate() invalidated the broad
org+connection key, which prefix-matches every test-runs query for the
connection. On the SSO Test step that meant revalidating the success
probe also refetched the visible test-runs list, spinning the "Refresh
logs" button (bound to the list's isFetching) whenever the user clicked
Continue.
Add an `exact` option to revalidate() that invalidates only the query's
own key, and use it for the Test step's probe-only revalidation on
Continue. The default broad behavior is unchanged, so "Refresh logs"
still refetches both the probe and the list.

@coderabbitaicoderabbitaiBot 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`:
- Around line 10-11: The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.
🪄 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), Repository UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: e79adfb9-1707-4186-8b5e-82f102f91f01

📥 Commits

Reviewing files that changed from the base of the PR and between 7450977 and 584c4df.

📒 Files selected for processing (4)
  • packages/shared/src/react/hooks/__tests__/useOrganizationEnterpriseConnectionTestRuns.spec.tsx
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/ui/src/components/ConfigureSSO/hooks/useEnterpriseConnectionTestRuns.ts
  • packages/shared/src/react/hooks/useOrganizationEnterpriseConnectionTestRuns.tsx

Comment on lines +10 to +11
const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Test never asserts the actual boolean result of revalidateHasSuccessfulTestRun().

The test only verifies that probeRevalidate was invoked with the right options — it never checks what revalidateHasSuccessfulTestRun() resolves to. Since the mock always returns { data: [], totalCount: 0 }, the boolean-derivation logic (the part that actually decides whether Continue can advance) is untested in both the "no successful run" and "successful run found" branches. Per the downstream snippet, TestConfigurationStep directly gates advance() on this resolved value, so an incorrect derivation here would silently break the revalidation feature.

As per coding guidelines, test files must "Verify proper error handling and edge cases" and tests should "Test component behavior, not implementation details" — this test currently only verifies implementation (spy calls), not the resulting behavior.

✅ Suggested additional coverage
 const probeRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
const listRevalidate = vi.fn(() => Promise.resolve({ data: [], totalCount: 0 }));
...
describe('useEnterpriseConnectionTestRuns', () => {
it('revalidateHasSuccessfulTestRun revalidates ONLY the probe, with exact invalidation', async () => {
...
});
++ it('resolves true when the revalidated probe contains a successful run', async () => {+ probeRevalidate.mockResolvedValueOnce({ data: [{ id: 'run_1', status: 'success' }], totalCount: 1 });+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(true);+ });++ it('resolves false when the revalidated probe has no successful run', async () => {+ const { result } = renderHook(() => useEnterpriseConnectionTestRuns(connection, true));++ let resolved: boolean | undefined;+ await act(async () => {+ resolved = await result.current.revalidateHasSuccessfulTestRun();+ });++ expect(resolved).toBe(false);+ });
});

Also applies to: 40-54

🤖 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/ui/src/components/ConfigureSSO/hooks/__tests__/useEnterpriseConnectionTestRuns.test.tsx`
around lines 10 - 11, The test around revalidation is only asserting the spy
calls and never verifies the boolean returned by
revalidateHasSuccessfulTestRun(), so the logic that determines whether a
successful test run exists is untested. Update the
useEnterpriseConnectionTestRuns test to assert the resolved value from
revalidateHasSuccessfulTestRun() for both the no-success and success cases,
using the existing probeRevalidate and listRevalidate mocks to drive each
branch. Focus the assertions on the behavior exposed by
useEnterpriseConnectionTestRuns and TestConfigurationStep rather than only on
implementation details like invocation arguments.

Source: Coding guidelines

@iagodahlem
iagodahlem merged commit 5028b54 into mainJul 2, 2026
53 checks passed
@iagodahlem
iagodahlem deleted the iago/orgs-1701-test-runs-on-continue branch July 2, 2026 13:17
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@iagodahlem@LauraBeatris