Skip to content

fix(opencode): retry transient network errors instead of surfacing as terminal with raw content#31440

Open
Sylchi wants to merge 1 commit into
anomalyco:devfrom
Sylchi:fix/retry-transient-network-errors
Open

fix(opencode): retry transient network errors instead of surfacing as terminal with raw content#31440
Sylchi wants to merge 1 commit into
anomalyco:devfrom
Sylchi:fix/retry-transient-network-errors

Conversation

@Sylchi

@Sylchi Sylchi commented Jun 9, 2026

Copy link
Copy Markdown

Issue for this PR

Closes #31133, closes #20822, closes #15350, closes #21893

Type of change

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

What does this PR do?

When a network request drops mid-flight (ECONNRESET, ECONNREFUSED, fetch failure, premature close, etc.), the error was classified as NamedError.Unknown and never retried — leaving the raw error body (potentially an HTML error page or thousands of characters) dumped on screen.

Two changes:

  1. Transport error detection in MessageV2.fromError() — Added isTransientNetworkError() helper that catches SystemError codes (ECONNRESET, ECONNREFUSED, ETIMEDOUT, ENOTFOUND, EAI_AGAIN, ENETUNREACH, EPIPE), TypeError from fetch, and common transient message patterns. These now produce an APIError with isRetryable: true so SessionRetry retries instead of surfacing a terminal error.

  2. Truncate long error response bodies in ProviderError.message() — When a proxy/gateway returns an HTML error page or any long non-JSON body, e.responseBody was embedded verbatim. Now truncated at 200 chars with "..." suffix.

How did you verify your code works?

  • Typecheck passes (bun typecheck in packages/opencode)
  • All 33 retry classification tests pass
  • All 4 account CLI tests pass
  • Full repo typecheck passes (23/23 packages)

Screenshots / recordings

N/A

Checklist

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

… terminal with raw content

When a network request drops mid-flight (ECONNRESET, ECONNREFUSED, fetch
failure, premature close, etc.), the error was classified as
NamedError.Unknown and never retried, leaving a large raw error body on
screen.

Two changes:

1. Add isTransientNetworkError() detection in MessageV2.fromError() that
   catches SystemError codes (ECONNRESET, ECONNREFUSED, ETIMEDOUT, ...),
   TypeError from fetch, and common transient message patterns. These now
   produce an APIError with isRetryable: true so SessionRetry retries.

2. Truncate e.responseBody in ProviderError.message() at 200 chars to
   prevent dumping long HTML error pages or raw content into the visible
   error message.

Closes anomalyco#31133, anomalyco#20822, anomalyco#15350, anomalyco#21893
@github-actions github-actions Bot added the needs:compliance This means the issue will auto-close after 2 hours. label Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

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

Based on the search results, I found one potentially related PR:

PR #30638 - fix(session): classify transport and timeout errors as retryable

The other results (PR #29627 and #19204) are related to error handling and retries but address different specific scenarios (undici TypeError, 5xx status codes) and appear to be distinct from this PR's focus.

@github-actions github-actions Bot removed the needs:compliance This means the issue will auto-close after 2 hours. label Jun 9, 2026
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Thanks for updating your PR! It now meets our contributing guidelines. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant