Skip to content

fix(core): forward retryAfterMs across provider session errors - #57589

Merged
rekram1-node merged 2 commits into
devfrom
session-error-retry-after
Oct 21, 2026
Merged

rekram1-node merged 2 commits into
devfrom
session-error-retry-after

Conversation

@rekram1-node

@rekram1-node rekram1-node commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Problem

SessionError.Error defines retryAfterMs?: NonNegativeInt, and @opencode/ai already parses retryAfterMs on RateLimitError, QuotaExceededError, and ProviderInternalError. However, toSessionError only copied retryAfterMs inside the FreeUsageLimitError branch, dropping it on provider.rate-limit, provider.quota, and provider.internal errors and re-checking Number.isFinite / >= 0 / Math.ceil downstream instead of typing retryAfterMs as NonNegativeInt at the schema and parser boundary.

Change

  1. Type retryAfterMs as NonNegativeInt on HttpRateLimitDetails, RateLimitError, QuotaExceededError, and ProviderInternalError, and round up in RequestExecutor.retryAfterMs at the HTTP header parsing boundary.
  2. Forward retryAfterMs directly in providerError() so every provider error reason that carries retryAfterMs populates SessionError.Error.retryAfterMs.
 RequestExecutor.httpFailure
-  retryAfterMs(headers) -> number (may be float)
+  retryAfterMs(headers) -> NonNegativeInt (Math.ceil at header parse boundary)
   classifyProviderFailure
     RateLimitError | QuotaExceededError | ProviderInternalError
       toSessionError
-        QuotaExceeded (FreeUsageLimitError only) -> SessionError.retryAfterMs (re-validated + Math.ceil)
-        RateLimit / QuotaExceeded / ProviderInternal -> dropped retryAfterMs
+        providerError -> forwards reason.retryAfterMs on all provider errors

Added by the opencode bot (AI-generated).

Related: #33955

@opencode-agent opencode-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Forwarding works. I ran RequestExecutor.httpFailure → toSessionError on both checkouts. On the base, retryAfterMs is dropped for rate-limit errors. With this PR, retry-after: 45 gives 45000 and retry-after-ms: 1500.5 gives 1501. core's session-error.test.ts and the full ai suite pass, and typecheck is clean in both packages.

One regression to fix before merging: the stricter schema type means a very large Retry-After value now throws inside httpFailure instead of producing a provider error (see inline).

Public API: SessionError.retryAfterMs was already NonNegativeInt, so the server, SDK and OpenAPI shapes don't change. Session errors now include retryAfterMs for provider.rate-limit, provider.quota and provider.internal too, which is additive. In @opencode/ai, retryAfterMs on RateLimitError, QuotaExceededError, ProviderInternalError and HttpRateLimitDetails narrows from any number to a non-negative safe integer. That is enforced when the error is built, so any caller passing a fraction or an unsafe integer will now get an exception.

Related: #33955

Comment thread packages/ai/src/route/executor.ts Outdated

const seconds = Number(value)
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000)
if (Number.isFinite(seconds)) return Math.max(0, Math.ceil(seconds * 1000))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NonNegativeInt is built on Schema.Int, which only accepts safe integers, and these error classes check their fields when constructed. So a large header value now makes httpFailure throw instead of returning a provider error. For example, retry-after: 1e13 (1e16 ms, above 2**53) or a huge retry-after-ms value causes new RateLimitError(...) to throw Schema validation failed. On the base this was a normal provider.rate-limit error. Could retryAfterMs cap the result here, e.g. Math.min(Number.MAX_SAFE_INTEGER, …) or a sane upper bound, so a buggy or hostile gateway header can't turn a 429 into an exception?

@rekram1-node
rekram1-node merged commit 80b8762 into dev Oct 21, 2026
17 checks passed
@rekram1-node
rekram1-node deleted the session-error-retry-after branch October 21, 2026 01:57
vleonbonnet added a commit to vleonbonnet/opencode that referenced this pull request Oct 21, 2026
- Tool output: upstream's new `save` (bytes a tool cannot attach inline) sits beside the fork's `truncateError`. The fork's private text saver is now `saveText`: its error path must catch a failed save, which upstream's `save` turns into a defect.
- Model requests: the `model.request` hook still sees `dryRun`; `baseURL` is passed as is, since upstream typed it as a string.
- Tests: the skill-instructions expectation for a changed description is upstream's new delta text, with the fork's list-of-rulesets `load`; the MCP test keeps both sides' imports.
- The OpenAPI documents (protocol and www) are regenerated: upstream's commit for `retryAfterMs` on session errors (anomalyco#57589) left them without 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.

1 participant