Skip to content

fix(session): retry transient transport failures - #38683

Open
literally-dan wants to merge 1 commit into
anomalyco:devfrom
literally-dan:transport-retries
Open

fix(session): retry transient transport failures#38683
literally-dan wants to merge 1 commit into
anomalyco:devfrom
literally-dan:transport-retries

Conversation

@literally-dan

Copy link
Copy Markdown

Issue for this PR

Closes #30611

This replaces #30638, which was closed by automated cleanup. I rebased the change on the current dev branch, tightened the transport error handling, and limited retries for these failures.

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Transport errors are often wrapped by the provider SDK, so checking only the top-level error misses failures such as dropped sockets and response timeouts. MessageV2.fromError now walks a bounded cause chain and recognizes transient Node, Bun, and Undici transport errors. Missing hosts, refused endpoints, and socket setup failures remain terminal. A plain AbortError still means the request was cancelled.

Transport retries stop after five attempts. Existing retry behavior for rate limits and provider errors is unchanged.

How did you verify your code works?

bun test test/session/message-v2.test.ts test/session/retry.test.ts passes 107 tests. bun typecheck also passes in packages/opencode.

Screenshots / recordings

Not applicable.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

@github-actions

Copy link
Copy Markdown
Contributor

The following comment was made by an LLM, it may be inaccurate:

Based on my search, I found one potentially related PR:

#34010 - fix(core): retry transient response stream errors

The searches for the specific issue numbers (#30611 and #30638) referenced in the PR description only returned the current PR itself, and the PR explicitly states it replaces #30638 which was closed by automated cleanup. The searches confirm there are no other open PRs with those issue references.

A dropped connection mid-turn failed the whole turn. Classify transport
faults by walking the error cause chain, which is where they end up once
the provider SDK wraps them, and retry them at most 5 times (2s, 4s, 8s,
16s, 30s) before surfacing. Previously only a top-level ECONNRESET was
recognised, so the same fault arriving as `TypeError: fetch failed` or
inside an APICallError was reported as an unknown error.

Bun's native fetch is the reason ConnectionRefused is transient rather
than permanent. It reports DNS failure, refused connection, TLS mismatch
and unroutable network with that one code and one shared message, so
treating it as permanent means a wifi flap, VPN reconnect or sleep/wake
hard-fails the turn. Bounded retry serves both cases: a genuinely wrong
baseURL still surfaces within about a minute. ENOTFOUND, ECONNREFUSED and
the DNS codes stay permanent because Node and undici only emit them for a
specific unresolvable endpoint.

Retrying replays the request unchanged, and nothing rolled back what the
failed attempt had already written, so a mid-stream drop left the
truncated output in the transcript with the full replacement appended
after it. Each attempt now tracks the parts it persisted and removes them
before the next one runs. A tool that already started executing is
different: deleting the part cannot undo what it did to the disk, so the
turn refuses to replay at all past that point.

Abort is also handled once rather than per branch. A cancel can surface
as a transport-shaped failure when it races the body read, so any
retryable classification is converted back into an abort when the turn is
known to be cancelled. That replaces the ZlibError-only guard and covers
the other transport branches, which previously would have retried.

An APICallError wrapping a transport fault now keeps its response
envelope. Short-circuiting to a bare network error discarded statusCode,
responseHeaders and the body, which cost the retry-after hint and the
only useful diagnostics.

Also caps ZlibError, header-timeout and SSE-stream-timeout retries, which
were already retryable but previously unbounded.
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.

Sessions fail on transient network errors instead of retrying

1 participant