Skip to content

fix(opencode): classify OpenRouter's typeless 5xx chunks as retryable APIError - #44

Open
PierrotAWB wants to merge 1 commit into
devfrom
andrew/openrouter-typeless-5xx
Open

PierrotAWB wants to merge 1 commit into
devfrom
andrew/openrouter-typeless-5xx

Conversation

@PierrotAWB

@PierrotAWB PierrotAWB commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Description

Follow-up to #43 (USE-2673). That PR handled the one typeless OpenRouter chunk that meant context overflow. OpenRouter uses the same shape for every relayed upstream failure, and in the last 30 days production saw three more of them, each ending a turn as a non-retryable UnknownError:

{ "code": 502, "message": "Stream ended before a terminal response event", "metadata": { "error_type": "provider_unavailable" } }
{ "code": 503, "message": "upstream connect error or disconnect/reset before headers. reset reason: connection timeout" }
{ "code": 504, "message": "Upstream idle timeout exceeded", "metadata": { "error_type": "timeout" } }

These are transient provider failures. The session retry loop already retries any APIError marked retryable or carrying a 5xx status, but parseStreamError never produced one for a body without type: "error".

This PR adds one rule after the overflow check: a typeless body whose numeric code is 5xx becomes a retryable APIError carrying the provider's message. The overflow rule stays first so an overflow 502 still compacts rather than retries.

Not shipped to the fleet until a release tag and an OPENCODE_VERSION bump in andytown.

Testing

  • bun test test/session/message-v2.test.ts: 38 pass, including the new case feeding the exact production 502 chunk through fromError and expecting a retryable APIError.
  • bun run typecheck in packages/opencode: clean.

🤖 Generated with Claude Code


Summary by cubic

Classifies OpenRouter's typeless 5xx chunks as retryable APIError so transient provider failures no longer end turns as non-retryable UnknownError.

Bug Fixes

  • parseStreamError now turns any typeless body with a numeric 5xx code into a retryable APIError carrying the provider's message.
  • The overflow check stays first, so an overflow 502 still compacts instead of retrying.
  • Not deployed until a release tag and OPENCODE_VERSION bump in andytown.

Written for commit 15eddc8. Summary will update on new commits.

Review in cubic

… APIError

OpenRouter relays upstream failures (provider unavailable, upstream connect
errors, idle timeouts) mid-stream as { code: 5xx, message } with no type
field, so parseStreamError returned undefined and the turn died as a
non-retryable UnknownError. Treat any typeless numeric 5xx code as a
retryable provider error so the session's retry loop handles it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

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

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/opencode/src/provider/error.ts">

<violation number="1" location="packages/opencode/src/provider/error.ts:120">
P2: Codes above 599 are not 5xx, but this condition marks every numeric code ≥500 as retryable and sends it through the session retry loop. Restrict the check to integer codes from 500 through 599.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

responseBody,
}
}
if (typeof body.code === "number" && body.code >= 500) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Codes above 599 are not 5xx, but this condition marks every numeric code ≥500 as retryable and sends it through the session retry loop. Restrict the check to integer codes from 500 through 599.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/opencode/src/provider/error.ts, line 120:

<comment>Codes above 599 are not 5xx, but this condition marks every numeric code ≥500 as retryable and sends it through the session retry loop. Restrict the check to integer codes from 500 through 599.</comment>

<file context>
@@ -117,6 +117,14 @@ export function parseStreamError(input: unknown): ParsedStreamError | undefined
       responseBody,
     }
   }
+  if (typeof body.code === "number" && body.code >= 500) {
+    return {
+      type: "api_error",
</file context>
Suggested change
if (typeof body.code === "number" && body.code >= 500) {
if (typeof body.code === "number" && Number.isInteger(body.code) && body.code >= 500 && body.code < 600) {

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant