Skip to content

fix(review): survive long reviews by bypassing undici bodyTimeout#62

Merged
JohnnyVicious merged 1 commit intomainfrom
fix/undici-body-timeout-terminated
Apr 14, 2026
Merged

fix(review): survive long reviews by bypassing undici bodyTimeout#62
JohnnyVicious merged 1 commit intomainfrom
fix/undici-body-timeout-terminated

Conversation

@JohnnyVicious
Copy link
Copy Markdown
Owner

Summary

  • Long adversarial reviews were dying at ~4.5 min with TypeError: terminated
  • Root cause: sendPrompt used fetch(), which is bound to Node's bundled undici. Undici enforces a 300_000 ms default bodyTimeout and kills the socket the moment the OpenCode server holds the response body open for >5 min while the model thinks — which is the normal case for long reviews on slow/free models. Our outer AbortSignal.timeout(600_000) is a wall-clock abort, not a dispatcher-level body timer, so it cannot prevent that kill.
  • Fix: replace the fetch() call with a direct node:http POST via a new httpPostJson helper. node:http has no equivalent hidden default, so the request survives until the server responds (or our own explicit wall-clock timer fires).
  • Per-call budget is now 30 min, overridable via OPENCODE_COMPANION_PROMPT_TIMEOUT_MS, matching the tracked-jobs 30-min hard timer.

Repro / evidence

Reproduced the exact error locally:

caught after 275.7s
0: TypeError - "terminated" - undefined
1: BodyTimeoutError - "Body Timeout Error" - UND_ERR_BODY_TIMEOUT

The server sent headers + a single body byte, then stalled. Undici destroyed the socket at its 300 s default, and fetch() surfaced the cause as TypeError: terminated — byte-for-byte the message the failing review logged.

Test plan

  • New regression test tests/send-prompt-body-timeout.test.mjs stalls the response body for 7 s and asserts the client tolerates it. Would have failed against the old fetch()-based implementation if its timeout were lowered below 7 s.
  • Full suite: npm test → 222/222 pass.

Long adversarial reviews were failing at ~4.5 minutes with
`TypeError: terminated`. Root cause: `sendPrompt` used `fetch()`,
and Node's bundled undici imposes a hidden 300_000 ms default
`bodyTimeout`. When OpenCode holds the response body open while
the model thinks on a large review, undici kills the socket and
surfaces the termination as an opaque "terminated" error — even
though our outer `AbortSignal.timeout(600_000)` was set to 10
minutes (it's a wall-clock abort, not a dispatcher-level body
timer, so it cannot prevent undici's internal kill).

Replace the `fetch()` call with a direct `node:http` request via
a new `httpPostJson` helper. `node:http` has no equivalent default
body timeout, so the request survives until the server responds
or our own explicit wall-clock timer fires. The per-call budget
is now 30 min, overridable via `OPENCODE_COMPANION_PROMPT_TIMEOUT_MS`,
matching the tracked-jobs hard timer.

Add a regression test that stalls the response body for 7 seconds
to prove we no longer cut long replies off.
@JohnnyVicious JohnnyVicious merged commit 7a522af into main Apr 14, 2026
1 check passed
@JohnnyVicious JohnnyVicious deleted the fix/undici-body-timeout-terminated branch April 14, 2026 15:34
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