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
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.
Bug
fetchWithRetrysends the sameRequestinstance on every attempt. The first attempt consumes the body stream, so any retry of a body-carrying request (POST/PUT/PATCH) fails withBody has already been usedinstead of retrying.This affects every
executetool API call:GlobalOutbound.fetch()passes aRequestobject intofetchWithRetry(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
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.tsonly use string URLs, which is why this was never caught.Repro
Fix
Send a
Request.clone()on every attempt that can still be retried, letting the final attempt consume the original. PR incoming.