You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
HTTP 408 is not retried on either path; 409 regressed since #39391 #47525
HTTP 408 Request Timeout is not treated as retryable on either request path, so a turn that hits one ends with an error and the prompt has to be resent by hand. 409 has the same problem on the v2 path.
This looks like a regression: #39391 (fix(ai): retry transient client statuses, merged 2026-07-28) made exactly these two statuses retryable in packages/ai/src/provider-error.ts:
retryable() only bypasses the SDK's isRetryable flag for status >= 500, so a 408 the SDK didn't mark retryable is dropped. This path is live: SessionRetry.policy is called from packages/opencode/src/session/processor.ts:675.
The newer RETRYABLE_MESSAGE_PATTERNS don't cover it either. The timeout pattern requires a literal space (request timeout), while the payload reported in #39221 carries request_timeout with an underscore. Running the reported message and responseBody against the current pattern list returns false for both.
Why 408 specifically
408 is the one 4xx that is transient in the same sense as a 5xx — the request never completed, so resending it is the defined behaviour for that status, not a retry of a rejected request. It shows up with OpenAI-compatible proxies that normalise an aborted upstream stream into 408 request_timeout.
Expected behavior
408 classified as retryable on both paths, taking the same backoff and retry-after handling as 5xx. 409 restored to the behaviour #39391 established, unless the reclassification to InvalidRequest was deliberate.
Notes
#39221 reported the v1 half and was closed as addressed by #39391, but the fix landed in the other module and has since been lost.
I have a PR ready for the v1 path and can follow up on the executor.ts half if the maintainers agree with the direction.
What is the issue?
HTTP 408 Request Timeout is not treated as retryable on either request path, so a turn that hits one ends with an error and the prompt has to be resent by hand. 409 has the same problem on the v2 path.
This looks like a regression: #39391 (
fix(ai): retry transient client statuses, merged 2026-07-28) made exactly these two statuses retryable inpackages/ai/src/provider-error.ts:packages/aihas since becomepackages/llm, and that behaviour did not come across.Where it stands on current
devv2 path —
packages/llm/src/route/executor.tsstatusReason()classifies 401, 403, 429, then400 | 404 | 409 | 413 | 422, thenstatus >= 500 || retryableStatus(status), where:UnknownProviderReason, whoseretryablegetter returnsfalse(packages/llm/src/schema/errors.ts).InvalidRequestReason, alsoretryable = false— the opposite of what fix(ai): retry transient client statuses #39391 established.v1 path —
packages/opencode/src/session/retry.tsretryable()only bypasses the SDK'sisRetryableflag forstatus >= 500, so a 408 the SDK didn't mark retryable is dropped. This path is live:SessionRetry.policyis called frompackages/opencode/src/session/processor.ts:675.The newer
RETRYABLE_MESSAGE_PATTERNSdon't cover it either. The timeout pattern requires a literal space (request timeout), while the payload reported in #39221 carriesrequest_timeoutwith an underscore. Running the reportedmessageandresponseBodyagainst the current pattern list returnsfalsefor both.Why 408 specifically
408 is the one 4xx that is transient in the same sense as a 5xx — the request never completed, so resending it is the defined behaviour for that status, not a retry of a rejected request. It shows up with OpenAI-compatible proxies that normalise an aborted upstream stream into
408 request_timeout.Expected behavior
408 classified as retryable on both paths, taking the same backoff and
retry-afterhandling as 5xx. 409 restored to the behaviour #39391 established, unless the reclassification toInvalidRequestwas deliberate.Notes
#39221 reported the v1 half and was closed as addressed by #39391, but the fix landed in the other module and has since been lost.
I have a PR ready for the v1 path and can follow up on the
executor.tshalf if the maintainers agree with the direction.