cloudflare / cloudflare/mcp

fetchWithRetry can't retry requests with a body (reuses a consumed Request)

Open
#196 1 comment 0 reactions 1 assignee Claimed by @mattzcarey View on GitHub
bug
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.