Skip to content

fix(harness): classify undici stream-timeout terminations as transient network failures#1038

Open
LEFTEQ wants to merge 1 commit into
vercel:mainfrom
LEFTEQ:fix/undici-timeout-transient
Open

fix(harness): classify undici stream-timeout terminations as transient network failures#1038
LEFTEQ wants to merge 1 commit into
vercel:mainfrom
LEFTEQ:fix/undici-timeout-transient

Conversation

@LEFTEQ

@LEFTEQ LEFTEQ commented Jul 21, 2026

Copy link
Copy Markdown

Fixes #1037.

Problem

When a provider/proxy response stream stalls past undici's headers/body timeout, the fetch is killed with TypeError: terminated (cause BodyTimeoutError / HeadersTimeoutError, codes UND_ERR_BODY_TIMEOUT / UND_ERR_HEADERS_TIMEOUT). The semantic-error catalog recognizes neither the codes nor the message, so classifyModelCallError falls through to "recoverable" — and in task mode the harness fails the run outright with zero retries. A transient transport stall becomes a terminal run failure (hit in production behind a LiteLLM proxy; details in the issue).

Fix

  • Add UND_ERR_BODY_TIMEOUT and UND_ERR_HEADERS_TIMEOUT to NETWORK_ERROR_CODES in semantic-errors/rules/system.ts — the coded rule wins whenever the cause chain survives.
  • Add "terminated" to the exact-equality message fallback, same stripped-cause rationale as "fetch failed" (undici's Fetch.terminate rejects with a TypeError whose message is exactly terminated). Equality, never containment.

Both signals now summarize as network-request-failed (tag transient) → classifyModelCallError returns "retry" and runModelCallWithRetries handles it like any other transient network failure.

Tests

  • semantic-errors.test.ts: coded shape (terminated + UND_ERR_BODY_TIMEOUT cause) and bare TypeError: terminated both classify as network-request-failed.
  • model-call-error.test.ts: the body-timeout shape classifies "retry" alongside the existing structural-signal cases.

pnpm vitest run on both files: 71 passed.

Note on commit signature: this commit carries a DCO sign-off but is not yet GPG/SSH-signed — happy to re-sign and force-push once my signing key is set up, or feel free to squash-merge.

…t network failures

When a provider response stream stalls past undici's headers/body timeout,
the fetch is killed with `TypeError: terminated` (cause BodyTimeoutError /
HeadersTimeoutError, codes UND_ERR_BODY_TIMEOUT / UND_ERR_HEADERS_TIMEOUT).
The semantic-error catalog did not recognize either signal, so
classifyModelCallError fell through to "recoverable" and a task-mode run
failed with zero retries — a transient transport stall became a terminal
run failure.

Add the two undici timeout codes to NETWORK_ERROR_CODES and "terminated"
to the exact-message fallback (same stripped-cause rationale as
"fetch failed").

Fixes vercel#1037

Signed-off-by: Lukáš Přibík <lukas.pribik@reservine.me>
@vercel

vercel Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Someone is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

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.

Harness: undici stream-timeout errors (TypeError: terminated) are classified 'recoverable', not 'retry' — task runs fail with zero retries

1 participant