Skip to content

feat(shared): Add cancellation and per-error delay options to retry#9182

Open
wobsoriano wants to merge 6 commits into
mainfrom
rob/shared-retry-cancellation
Open

feat(shared): Add cancellation and per-error delay options to retry#9182
wobsoriano wants to merge 6 commits into
mainfrom
rob/shared-retry-cancellation

Conversation

@wobsoriano

@wobsoriano wobsoriano commented Jul 16, 2026

Copy link
Copy Markdown
Member

Description

Follow-up to #9173, which added a hand-rolled exponential backoff to Expo's initialization resource recovery because the shared retry helper couldn't express two things it needed. This adds them:

  • signal?: AbortSignal: cancels retrying. Aborting rejects the returned promise with the signal's abort reason and immediately interrupts a pending backoff delay. It does not abort a callback that is already executing.
  • initialDelay now also accepts a function (error, iteration) => number, deriving the backoff base delay from the error that triggered the retry (e.g. 2s for network errors, 10s for 5xx). The exponential factor, cap, and jitter still apply.

Both changes are optional and additive. Defaults and existing behavior are unchanged, and no current retry consumer is affected.

A follow-up PR will migrate the Expo recovery logic from #9173 onto this helper, gaining jitter on its retry delays.

Checklist

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

Type of change

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

Summary by CodeRabbit

  • New Features

    • Retry operations can now be cancelled using an AbortSignal.
    • Retry delays can be calculated dynamically using either a fixed delay or a function based on the error and retry attempt.
    • If cancellation occurs, any pending retry delay stops promptly.
  • Bug Fixes

    • Cancelled retries now consistently reject with the appropriate abort reason and prevent additional retry attempts (including avoiding further retry hooks after abort).
  • Tests

    • Added coverage for cancellation timing and dynamic delay behavior.

Adds an optional signal (AbortSignal) option so callers can cancel
retrying promptly, and allows initialDelay to be a function deriving
the backoff base delay from the error that triggered the retry.
@changeset-bot

changeset-bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: e841a9d

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

This PR includes changesets to release 23 packages
Name Type
@clerk/shared Patch
@clerk/astro Patch
@clerk/backend Patch
@clerk/chrome-extension Patch
@clerk/clerk-js Patch
@clerk/electron Patch
@clerk/expo-passkeys Patch
@clerk/expo Patch
@clerk/express Patch
@clerk/fastify Patch
@clerk/headless Patch
@clerk/hono Patch
@clerk/localizations Patch
@clerk/msw Patch
@clerk/nextjs Patch
@clerk/nuxt Patch
@clerk/react-router Patch
@clerk/react Patch
@clerk/tanstack-react-start Patch
@clerk/testing Patch
@clerk/ui Patch
@clerk/vue Patch
@clerk/swingset Patch

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

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

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Error Error Jul 17, 2026 4:37am
swingset Error Error Jul 17, 2026 4:37am

Request Review

The `retry` helper now supports cancellation and error-dependent backoff. Added `signal` option for cancellation and modified `initialDelay` to accept a function for backoff base delay.
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The shared retry helper now supports AbortSignal cancellation, abort-aware delays, and error-dependent initial backoff calculation. Tests cover cancellation timing and dynamic delay bases, with a changeset documenting the patch.

Changes

Retry cancellation and dynamic backoff

Layer / File(s) Summary
Retry options and abort-aware backoff
packages/shared/src/retry.ts
retry accepts an AbortSignal, interrupts pending delays, stops subsequent attempts after cancellation, and supports function-based initialDelay values derived from errors and iterations.
Retry behavior validation and release guidance
packages/shared/src/__tests__/retry.spec.ts, .changeset/shared-retry-cancellation.md
Tests cover pre-aborted, in-flight, and delayed cancellation plus error-derived backoff delays; the changeset records the updated retry behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant retry
  participant Callback
  participant Delay
  participant AbortSignal

  Caller->>retry: Start retry with options
  retry->>Callback: Execute attempt
  Callback-->>retry: Throw error
  retry->>Delay: Schedule error-derived backoff
  AbortSignal-->>Delay: Abort with reason
  Delay-->>retry: Reject pending delay
  retry-->>Caller: Reject with abort reason
Loading

Suggested reviewers: nikosdouvlis, mikepitre

Poem

I’m a bunny with retries tucked tight,
Abort hops in and ends the night.
Errors tune the delays just right,
Tests watch each timer’s flight.
Patch notes bloom in code’s moonlight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding cancellation and per-error delay support to the shared retry helper.
✨ 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-new Bot commented Jul 16, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/electron

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

@clerk/electron-passkeys

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

@clerk/eslint-plugin

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

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: eb3289e

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/shared/src/retry.ts`:
- Around line 5-11: Update the JSDoc for the public initialDelay callback
parameter in retry configuration to explicitly state that iteration starts at 1,
clarifying its indexing semantics without changing the callback type or
behavior.
- Around line 138-142: Update the retry loop around the callback’s catch
handling to recheck signal cancellation immediately after the callback rejects,
before returning the original error or invoking retry hooks; when aborted, throw
abortReason(signal) to preserve the cancellation contract. Add a regression test
covering a callback that aborts the signal while running and then rejects.
🪄 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: fa8bdc4b-3b48-4f3e-8b60-0c1aca0aa78a

📥 Commits

Reviewing files that changed from the base of the PR and between b0ed361 and 401b36c.

📒 Files selected for processing (3)
  • .changeset/shared-retry-cancellation.md
  • packages/shared/src/__tests__/retry.spec.ts
  • packages/shared/src/retry.ts

Comment thread packages/shared/src/retry.ts
Comment on lines 138 to +142
while (true) {
if (signal?.aborted) {
throw abortReason(signal);
}

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

Recheck cancellation immediately after the callback rejects.

If the signal aborts while callback is running, the catch block can return the original error or invoke retry hooks before observing cancellation. This violates the documented guarantee that cancellation rejects with the abort reason.

Proposed fix
     } catch (e) {
+      if (signal?.aborted) {
+        throw abortReason(signal);
+      }
+
       iterations++;

Add a regression test where the signal aborts during an active callback and that callback subsequently rejects.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while (true) {
if (signal?.aborted) {
throw abortReason(signal);
}
if (signal?.aborted) {
throw abortReason(signal);
}
🤖 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/shared/src/retry.ts` around lines 138 - 142, Update the retry loop
around the callback’s catch handling to recheck signal cancellation immediately
after the callback rejects, before returning the original error or invoking
retry hooks; when aborted, throw abortReason(signal) to preserve the
cancellation contract. Add a regression test covering a callback that aborts the
signal while running and then rejects.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-07-16T20:38:46.758Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 0
🔴 Breaking changes 0
🟡 Non-breaking changes 0
🟢 Additions 0

No API Changes Detected

All packages have stable APIs with no detected changes.


Report generated by Break Check

Last ran on eb3289e.

@wobsoriano
wobsoriano requested review from a team and mikepitre and removed request for a team July 16, 2026 19:40
Comment thread packages/shared/src/retry.ts Outdated
wobsoriano and others added 2 commits July 16, 2026 21:37
Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
@macroscopeapp

macroscopeapp Bot commented Jul 17, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved

Additive feature adding optional signal and functional initialDelay parameters to the retry utility. Changes are backward compatible, well-tested, and both review comments are already addressed in the current code.

You can customize Macroscope's approvability policy. Learn more.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@packages/shared/src/retry.ts`:
- Around line 150-151: Remove the duplicated catch clause in the retry logic so
the surrounding try statement has only one `catch (e)` block. Preserve the
existing catch body and error-handling behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 103d6fe9-86f8-4115-80a0-a299e85963ce

📥 Commits

Reviewing files that changed from the base of the PR and between 401b36c and e841a9d.

📒 Files selected for processing (2)
  • packages/shared/src/__tests__/retry.spec.ts
  • packages/shared/src/retry.ts

Comment on lines 150 to +151
} catch (e) {
} catch (e) {

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 | 🔴 Critical | ⚡ Quick win

Duplicate catch block is a syntax error.

Line 150 and Line 151 both contain } catch (e) {, which is invalid JavaScript — a try block can only have one catch clause. This will cause a parse error and prevent the module from compiling. Biome also flags this: "Expected a statement but instead found 'catch (e)'."

This appears to be a merge artifact or copy-paste error. Remove the duplicate line.

🐛 Proposed fix
     } catch (e) {
-    } catch (e) {
       if (signal?.aborted) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} catch (e) {
} catch (e) {
} catch (e) {
🧰 Tools
🪛 Biome (2.5.3)

[error] 151-151: Expected a statement but instead found 'catch (e)'.

(parse)

🤖 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/shared/src/retry.ts` around lines 150 - 151, Remove the duplicated
catch clause in the retry logic so the surrounding try statement has only one
`catch (e)` block. Preserve the existing catch body and error-handling behavior.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant