fetchWithRetry can't retry requests with a body (reuses a consumed Request)
- Dominant language
- TypeScript
- Stars
- 833
- Forks
- 116
- Avg merge
- 7h 19m
- Merged PRs (30d)
- 3
Description
## Bug
`fetchWithRetry` sends the same `Request` instance on every attempt. The first attempt consumes the body stream, so any retry of a body-carrying request (POST/PUT/PATCH) fails with `Body has already been used` instead of retrying.
This affects every `execute` tool API call: `GlobalOutbound.fetch()` passes a `Request` object into `fetchWithRetry` (`src/tools/execute.ts` → `src/utils/fetch-retry.ts`), so retry on rate limits is effectively broken for the server's write operations.
## Observed behavior
- After a **429**: the loop sleeps through every backoff delay, each retry throws internally, and the original 429 response is returned anyway — the retries can never succeed.
- After a **network error**: the retry throws `TypeError: Body has already been used`, masking the real network error.
GET requests (no body) are unaffected, and the existing tests in `tests/fetch-retry.test.ts` only use string URLs, which is why this was never caught.
## Repro
```ts
const request = new Request('https://api.cloudflare.com/client/v4/accounts/xxx/d1/database', {
method: 'POST',
body: '{"name":"my-db"}'
})
// First attempt gets a 429 → every subsequent attempt fails with
// "Body has already been used" instead of resending the body.
await fetchWithRetry(request)
```
## Fix
Send a `Request.clone()` on every attempt that can still be retried, letting the final attempt consume the original. PR incoming.
Contributor guide
Assessment
This issue has not been assessed yet.