anomalyco / anomalyco/opencode
webfetch timeout does not cover the response body read
@neriousy is already working on this.
Since Aug 26, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
webfetch's timeout is applied to the request but not to the body read, so a server that answers headers promptly and then stalls mid-body hangs forever. Two smaller defects sit alongside it in the same expression.
packages/opencode/src/tool/webfetch.ts:
const response = yield* httpOk.execute(request).pipe(
Effect.catchIf(/* cloudflare retry */),
Effect.timeoutOrElse({ duration: timeout, orElse: () => Effect.die(new Error("Request timed out")) }),
)
// Check content length
const contentLength = response.headers["content-length"]
...
const arrayBuffer = yield* response.arrayBuffer // <-- outside the timeout
1. The timeout does not cover the body. timeoutOrElse closes over httpOk.execute(request) only. response.arrayBuffer runs after it, unguarded. Headers arriving within 30s satisfy the timeout no matter how long the body takes, and a slow-drip response never returns.
2. Effect.die instead of Effect.fail. A timeout is an expected outcome of fetching an arbitrary URL, not a defect. Effect.die raises it as an unrecoverable defect rather than a typed error the tool layer can turn into a message the model reads and acts on.
3. timeout has no lower bound. The parameter is Schema.optional(Schema.Number), so 0 and negatives are accepted. Math.min((params.timeout ?? 30) * 1000, MAX_TIMEOUT) clamps the ceiling but not the floor, so timeout: 0 produces a 0 ms deadline that aborts the request immediately and then dies per (2).
The v2 tool already gets all three right
packages/core/src/tool/webfetch.ts wraps the request and collectBody(response) in one Effect.gen before applying the timeout, fails instead of dying, and bounds the input:
}).pipe(
Effect.timeoutOrElse({
duration: Duration.seconds(input.timeout ?? DEFAULT_TIMEOUT_SECONDS),
orElse: () => Effect.fail(new Error("Request timed out")),
}),
)
const Timeout = Schema.Number.check(Schema.isGreaterThan(0), Schema.isLessThanOrEqualTo(MAX_TIMEOUT_SECONDS))
The registered v1 tool is the one that predates it.
Steps to reproduce
- Point
webfetchat an endpoint that returns headers immediately and then writes the body slowly (or not at all). - The call does not return after the timeout elapses.
For (3): call webfetch with timeout: 0 on any URL — the request is aborted immediately.
Operating System
Windows 11 (platform independent)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.