Skip to content

fix: made timeout and retry option compatible - #638

Open
timhn-bm wants to merge 1 commit into
unjs:mainfrom
timhn-bm:main
Open

timhn-bm wants to merge 1 commit into
unjs:mainfrom
timhn-bm:main

Conversation

@timhn-bm

@timhn-bm timhn-bm commented Oct 15, 2026 •

Copy link
Copy Markdown

Fixes #637

Bug

When passing retry and timeout, if one of the attempted request timed out, the subsequent HTTP calls were never sent. This PR fixes the issue

Solution

The bug lied in how the signal option was propagated from one attempt to the other. With the timeout option, we were forwarding the full previous AbortSignal which included the previous AbortSignal.timeout. If the 1st request had timed out, we are passing to the 2nd request a AbortSignal which contained the previous - already aborted - AbortSignal. So the 2nd HTTP request was never sent.

Re-creating a fetchOptions without forwarding the whole signal from one attempt to the other fixes the issue

Summary by CodeRabbit

  • Bug Fixes
    • Requests with configured timeouts now preserve existing abort signals while applying the timeout.
    • Timed-out requests retry as configured and report a timeout error after all attempts.

@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 82ec5701-436e-4e53-b849-60a4e6e0f02f
📥 Commits

Reviewing files that changed from the base of the PR and between 1dbc37f and c8e706b.

📒 Files selected for processing: 2
  • src/fetch.ts
  • test/index.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Walkthrough

When a timeout is configured, fetch passes a copied options object containing the timeout signal combined with any existing signal. A test verifies the timeout error and confirms that three retries result in four requests to the timeout handler.

Changes: Timeout retries

Layer / File(s) Summary
Keep timeout signals out of shared retry options
src/fetch.ts, test/index.test.ts
fetch uses a copied options object when it combines timeout and existing signals. The timeout test checks the error message, error name, error code, and four handler calls. The expected JSON value is unchanged.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to c8e70

The change appears ready to merge after normal checks. The timeout test’s timing may warrant monitoring, but no failure has been established.

### Architecture Summary

Architecture risk: 🔵 Low · up to c8e70

The change affects 2 systems.

Changed systems: src, test

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — src (service) was modified; 1 changed file maps to changed impact.
  • observed — test (service) was modified; 1 changed file maps to changed impact.

Before / after behavior

  • observed — Modified behavior in src/fetch.ts: Added the ResolvedFetchOptions type import used to type the options passed to fetch.
  • observed — Modified behavior in src/fetch.ts: When a timeout is configured, fetch now receives a copied options object with the timeout signal combined with any existing signal; previously, the timeout signal was assigned to context.options.signal and that same options object was passed to fetch.
  • observed — Modified behavior in test/index.test.ts: Adds a counter for requests handled by the /timeout endpoint.
  • observed — Modified behavior in test/index.test.ts: Increments the timeout request counter each time the /timeout handler runs.
Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly describes the main change: making timeout and retry behavior compatible so retries continue after a timeout.
Linked Issues check Passed Issue #637 requires retries to send subsequent HTTP requests after a timeout when retry and timeout are both set. src/fetch.ts creates a new fetchOptions object for each attempt and creates a …
Out of Scope Changes check Passed The reviewed changes are limited to timeout-signal handling in src/fetch.ts and a focused regression test in test/index.test.ts. The test-server counter and response-format change support or do no…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

AbortSignal.timeout was forwarded from the 1st call to the next ones. When executing a subsequent try fetch, the HTTP request was never sent as the signal had already been aborted
Comment thread test/index.test.ts

const fetch = vi.spyOn(globalThis, "fetch");

let timeoutCallsCount = 0;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

note
This seems a bit dirty to count how often the /timeout call is called. Happy to get better suggestions for this

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.

retry option not compatible with timeout

1 participant