Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 6, 2025

Fix RetryableError retryAfter to use milliseconds like sleep()

Problem

The retryAfter property of RetryableError currently treats numeric values as seconds, but the sleep() function treats numeric values as milliseconds. This inconsistency makes the API confusing for users.

Solution

Changed RetryableError to treat numeric retryAfter values as milliseconds, matching the behavior of sleep().

Plan

  • Update RetryableError constructor to treat numeric retryAfter values as milliseconds (not seconds)
  • Update documentation comments to reflect this change
  • Review and update any example code that uses numeric retryAfter values
  • Create tests to verify the fix works correctly
  • Build and verify all changes work as expected
  • Add changeset documenting the breaking change
  • Update documentation examples to use milliseconds
  • Code review completed with no issues
  • Security scan completed with no vulnerabilities
  • Simplify tests per feedback
  • Use string durations in documentation examples

Changes Made

  1. Updated RetryableError implementation (packages/errors/src/index.ts):

    • Changed numeric retryAfter to be treated as milliseconds instead of seconds
    • Updated variable names from retryAfterSeconds to retryAfterMs for clarity
    • Default retry is now 1000 milliseconds instead of 1 second (functionally same, but clearer)
  2. Updated documentation (docs/content/docs/foundations/errors-and-retries.mdx):

    • Changed JSDoc comments to clarify that numbers are milliseconds (same as sleep())
    • Updated examples to use string durations for HTTP Retry-After headers (e.g., ${retryAfter}s)
    • Removed unnecessary explanatory comments
  3. Simplified tests (packages/errors/src/index.test.ts):

    • Removed unnecessary jitter/timing checks
    • Focused tests on verifying conversions work correctly
    • Reduced from 12 tests to 10 tests with clearer intent
    • All tests pass ✓
  4. Added changeset (.changeset/retryable-error-milliseconds.md):

    • Documents the breaking change
    • Provides migration examples

Breaking Change Note

This is a breaking change for code that uses numeric retryAfter values. Users need to multiply by 1000 to convert from seconds to milliseconds.

Migration Example:

// Before
throw new RetryableError("Error", { retryAfter: 5 }); // 5 seconds

// After  
throw new RetryableError("Error", { retryAfter: 5000 }); // 5000 milliseconds
// OR use string duration (preferred)
throw new RetryableError("Error", { retryAfter: "5s" });

String durations (like "5s") and Date objects are unaffected.

Verification

✓ All tests pass (10/10)
✓ Code builds successfully
✓ TypeScript types are correct
✓ Code review feedback addressed

Security Summary

No security vulnerabilities were introduced or discovered.

Original prompt

the retryAfter property of RetryableError should support the same signature as the arguments to sleep. For example, when returning a number, it should be the number of milliseconds (just like sleep). Currently, retryAfter is in seconds which is inconsnstent


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

@changeset-bot
Copy link

changeset-bot bot commented Nov 6, 2025

🦋 Changeset detected

Latest commit: 009dd49

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

This PR includes changesets to release 13 packages
Name Type
@workflow/errors Patch
@workflow/builders Patch
@workflow/cli Patch
@workflow/core Patch
workflow Patch
@workflow/world-postgres Patch
@workflow/world-vercel Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/sveltekit Patch
@workflow/world-testing Patch
@workflow/web-shared Patch
@workflow/ai 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
Copy link
Contributor

vercel bot commented Nov 6, 2025

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

Project Deployment Preview Comments Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview Comment Nov 6, 2025 7:29am
example-nextjs-workflow-webpack Ready Ready Preview Comment Nov 6, 2025 7:29am
example-workflow Ready Ready Preview Comment Nov 6, 2025 7:29am
workbench-nitro-workflow Ready Ready Preview Comment Nov 6, 2025 7:29am
workbench-nuxt-workflow Ready Ready Preview Comment Nov 6, 2025 7:29am
workbench-sveltekit-workflow Ready Ready Preview Comment Nov 6, 2025 7:29am
workbench-vite-workflow Ready Ready Preview Comment Nov 6, 2025 7:29am
workflow-docs Ready Ready Preview Comment Nov 6, 2025 7:29am

Copilot AI changed the title [WIP] Update retryAfter property to support milliseconds Fix RetryableError to treat numeric retryAfter as milliseconds (consistent with sleep) Nov 6, 2025
Copilot finished work on behalf of pranaygp November 6, 2025 04:08
Copilot AI requested a review from pranaygp November 6, 2025 04:08
// Delay the retry until after a timeout
throw new RetryableError("Too many requests. Retrying...", { // [!code highlight]
retryAfter: parseInt(retryAfter), // [!code highlight]
retryAfter: parseInt(retryAfter) * 1000, // Convert seconds to milliseconds // [!code highlight]
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot use the string version here for seconds and get rid of the extra comment. it's self explanatory

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 009dd49 - now using string duration format ${retryAfter}s instead of multiplying by 1000.


if (response.status >= 500) {
// Exponential backoffs
// Exponential backoffs (in milliseconds)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot no need for this explanation- self explanatory

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 009dd49.

// Delay the retry until after a timeout
throw new RetryableError("Too many requests. Retrying...", {
retryAfter: parseInt(retryAfter),
retryAfter: parseInt(retryAfter) * 1000, // Convert seconds to milliseconds
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot no need for comment here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed in 009dd49.

@@ -0,0 +1,147 @@
import { describe, expect, it } from 'vitest';
import { RetryableError } from './index.js';

Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot these tests are stupid. the before vs after jitter check is unnecessary. all we should be trying to test is if the various signatures work and get converted to times correctly. simplify the tests to just test what's necessary and not create a bunch of unneeded tests

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplified in 009dd49 - removed jitter checks and unnecessary tests. Now just testing that each signature (number, string, Date) converts correctly. Reduced from 12 tests to 10 focused tests.

@pranaygp pranaygp requested a review from TooTallNate November 6, 2025 07:22
@pranaygp pranaygp marked this pull request as ready for review November 6, 2025 07:22
Copy link
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

Whoops, looks like we did some duplicate work. I think I prefer mine (#240) since it re-uses the parseDurationToDate() util function that sleep() uses. Close?

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.

3 participants