fix(sse): do not replay a malformed 200 on the same account - #14955
Merged
diegosouzapw merged 2 commits intoSep 28, 2026
Conversation
chatCore turns a non-streaming upstream HTTP 200 whose translated body carries no usable output into a 502 (malformed_translated_response) with error.type "upstream_response_error". The single-model pre-output transport retry (diegosouzapw#9708) treated every 502 as a transport blip and replayed the request once on the same account, so a call the upstream had already answered (and likely billed) was paid for twice for the same verdict. Export the marker type as UPSTREAM_RESPONDED_ERROR_TYPE from diagnostics.ts and list it in the transport retry's non-retryable types. Genuine pre-output 502/503/504/507 failures, and upstream-sent 502s without the local marker, keep their single same-account retry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
chatCoreturns a non-streaming upstream HTTP 200 into a502(malformed_translated_response) when the translated body has no usable output. Itserror.typeisupstream_response_error, and the code isupstream_empty_response,upstream_response_failedorupstream_fake_success.502as a transport failure that happened before any output. So it waited 2-3 s and sent the request to the same account again, even though the upstream had already answered that call with a body (and most likely billed it).diagnostics.tsnow exports this type asUPSTREAM_RESPONDED_ERROR_TYPE(same value), and the transport retry lists it as non-retryable. These still get their one same-account retry:502/503/504/507)502s sent by an upstream without the local markererror.type, so the response shape does not change.Related Issues
Validation
node --import tsx/esm --test tests/unit/malformed-translated-no-same-account-retry.test.ts. 6/6 pass with the fix. With the previoussameAccountTransportRetry.ts, the 3 malformed-200 cases fail.release/v3.8.51Tests Added Or Updated
tests/unit/malformed-translated-no-same-account-retry.test.ts(new)Coverage Notes
chatCorereturns, usingdescribeMalformedNonStreamandcreateErrorResult. It checks thatshouldRetrySameAccountTransportreturnsfalse. Control cases pin the retry behavior that does not change.Reviewer Notes
markAccountUnavailable, transient502cooldown, rotation to the next account if one exists). Before, it went there only after the same-account retry also failed. So the account is rotated one attempt earlier, and the same-account replay goes away.chat.tsand the combo files are not touched. Both are frozen bycheck:file-size.upstream_fake_successis not request-scoped, soexecuteTargetAttemptstill retries the same target once when model lockout is off (the default).upstream_empty_responseandupstream_response_failedare already request-scoped there and are not retried on the same target. A follow-up can add the same marker check toisTransient.