Skip to content

Keep webFetch's timeout armed until the response body has been read - #40

Closed
snowyukitty wants to merge 1 commit into
cloudflare:mainfrom
snowyukitty:fix/webfetch-body-timeout
Closed

Keep webFetch's timeout armed until the response body has been read#40
snowyukitty wants to merge 1 commit into
cloudflare:mainfrom
snowyukitty:fix/webfetch-body-timeout

Conversation

@snowyukitty

@snowyukitty snowyukitty commented Aug 6, 2026

Copy link
Copy Markdown

Refs #27 — this covers the timeout half only, so please keep that issue open for the rest (see the
list at the bottom).

fetch() resolves as soon as the response headers arrive, and the finally cleared
FETCH_TIMEOUT_MS right there — before readBodyCapped() ran. The declared 30s bound therefore
only covered connect and time-to-headers, and the body read had no deadline at all.
readBodyCapped is a bare while (true) { await reader.read() } with no signal, so a server that
answers promptly and then stalls holds the agent turn open with nothing to stop it.

The 1 MiB cap does not substitute for a deadline: it bounds total bytes, so a server trickling
below the cap never trips it.

Measured on workerd, against a server that flushes headers then writes one byte every 2s, with
the timeout shortened to 5s:

variant result
before this PR still pending when the client gave up at 25s
just keeping the timer armed AbortError after 5057ms
this PR Error: Fetch timed out after 5000ms after 5039ms

The middle row is why the diff is not a one-line move: the abort does reach an in-flight
reader.read(), but the catch that produces the friendly message wraps only the fetch call, so
a timeout during the body read would otherwise surface as a bare AbortError. The change clears
the timer on each exit path and maps an abort during the body read onto the same error.

Two things this deliberately leaves alone, both because they change the function's signature or its
caller, and both noted in #27:

  • The AbortSignal pi-agent-core passes to a tool as execute's third argument is still dropped
    by the webFetch tool, so Stop still cannot cancel a slow fetch.
  • convertToMarkdown runs after the body read, so it stays outside the deadline.

Verified: pnpm lint:check and pnpm --filter @gadgets/workshop-backend types:check pass.

`fetch()` resolves as soon as the response headers arrive, and the `finally`
cleared the timeout right there -- before `readBodyCapped()` ran. The declared
30s bound therefore only covered connect and time-to-headers, and the body read
had no deadline at all. The 1 MiB cap does not substitute for one: it bounds
total bytes, so a server that answers promptly and then trickles below the cap
is never cut off.

Measured against a server that flushes headers and then writes one byte every
2s, with the timeout shortened to 5s: today's code was still pending when the
client gave up at 25s; with this change it fails at 5039ms.

Clear the timer on each exit path instead, and map an abort during the body read
onto the same "Fetch timed out" error -- the existing catch wraps only the fetch
call, so otherwise a timeout there surfaces as a bare AbortError.

Not addressed here, since both change the function's signature or its caller:
the abort signal pi-agent-core passes to the tool is still dropped, so Stop
cannot cancel a slow fetch; and convertToMarkdown still runs outside the
deadline.
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@snowyukitty

Copy link
Copy Markdown
Author

recheck

@snowyukitty

Copy link
Copy Markdown
Author

Closing this in deference to CONTRIBUTING.md and your note on #39.

The finding and the workerd measurements are in #27, which stands on its own — same diff, the
four-variant timing table, and the two related findings this PR deliberately left out. No review
requested here; issues only from here on.

@snowyukitty snowyukitty closed this Aug 7, 2026
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.

1 participant