fix: the non-stream deadline, conversation-sticky variants and credential faults - #124
Merged
Merged
Conversation
This was referenced Sep 23, 2026
CMGS
force-pushed
the
fix/closure-followups
branch
from
September 23, 2026 11:58
4d3cb90 to
e6f2c23
Compare
…t cap The per-account timeout (default 60s) bounded a non-streaming attempt whole. With max_tokens defaulting to 16384 on models that think by default, a long answer outran it: the request timed out, failed over, timed out again and cooled both accounts, where the old 1024 cap had truncated in time. The deadline is now at least the account's timeout and grows with the request's max_tokens at 128k tokens per hour, at most ten minutes: the rate and ceiling the Anthropic SDK budgets for non-streaming calls. The cap rides the upstream request from the Anthropic wire and OpenAI chat; streams keep their header deadline and idle cap.
A thinking replay from a client with no user id was pinned to the requested model while its first turn had been split like any request, so a canary rollout switched models mid tool loop. The split is now keyed by the conversation's first user turn when no user id is present: a signed replay must carry that prefix verbatim, so every turn lands where the first one did. A reasoning continuation with neither key (a native Responses input) still pins.
A vendor 401, 402 or 403 answers the account's own state, not the request, yet neither engine arm counted it: the first attempt neither cooled the account nor failed over, and the retry arm only cooled it by accident until the 5xx gate. A dead account kept its share of traffic and answered 424 to every call. Both arms now treat 401-403 as an account fault, the same as a 5xx: cool it, try the next account, and walk the fallback chain when none is left.
CMGS
force-pushed
the
fix/upstream-contract-followups
branch
from
September 23, 2026 12:04
ed42632 to
600c6c6
Compare
This was referenced Sep 23, 2026
Chat-surface requests served by a Responses model are stored by OpenAI (store defaults to true)
#114
Closed
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
Stacked on #123. The three design items #123's review round left open, each re-verified against the source and fixed with a failing-first test and a live before/after.
max_tokensdefaulting to 16384 on models that think by default (#123), a long answer outran it: timeout, failover, a second timeout, both accounts cooled, where the old 1024 cap had truncated in timetimeout_seconds: 20,claude-opus-5-5non-streaming, nomax_tokens, a 1200-word essay: 408model_timeout_exceptionafter 20 sfinish_reason: stop, 3764 completion tokens (the 16384 cap budgets 461 s)claude-opus-5canaried toclaude-opus-5-5, no user id, a tool turn: turn 1 served by 5-5 with a thinking block, the replay served byclaude-opus-5claude-opus-5-5failure_threshold: 1: two requests, both 424 (vendor 401), the good account never triedDesign notes:
max(timeout_seconds, max_tokens at 128k tokens/hour), capped at ten minutes: the rate and ceiling the Anthropic SDK budgets for non-streaming calls.timeout_secondsstays the floor, so an operator's 120s still holds; streams keep their header deadline and idle cap. The cap rides the upstream request from the Anthropic wire and OpenAI chat; the Bedrock paths keep the plain timeout.input) still pins to the requested model.429stays a throttle (no cooling), and an in-bandinsufficient_quotathat arrives as HTTP 429 is not distinguished.Verification
non_stream_deadline_grows_with_the_output_cap,a_conversation_without_a_user_id_sticks_to_one_variantwiththinking_conversations_stay_on_one_variant_and_off_quota_fallbacks,a_credential_refusal_fails_over_and_cools_the_accountover 401/402/403) fails with its fix reverted and passes with it.git archive | tar -m), passescargo fmt --all -- --check,cargo clippy --workspace --all-targets -- -D warningsandcargo test --workspace; the test count rises 676 → 679.rust:1linux/arm64 container: rustc 1.98.0, fmt ✓, clippy ✓, 679 passed, 0 failed, 4 ignored (the env-gated Postgres/Redis suites were not run; this PR touches no store).claude-opus-5canaried toclaude-opus-5-5; B4 with an invalid key on the preferred account. The full live matrix was run on fix: close the flow-read defects (#112–#121) with live before/after #123's binary; this PR changes no wire body, only the deadline, the split key and the fault gate.max/multiply per non-streaming upstream call (the deadline), onefindover the messages on the variant path only when no user id is present (the first message is normally the first user turn), and one status compare on the error path. An ABBA bench against fix: close the flow-read defects (#112–#121) with live before/after #123 (in-process mock upstream, c=64, 6 rounds of 10 s) read chat→Claude −0.3%, buffered messages +0.1%, Converse stream +0.4% and Responses stream +0.5% requests/s, p50 within ±0.02 ms; an A/A control on the same host read +0.2% / −0.0% / −2.9% / +0.5%, so every change is within noise. The bench binary predates two layout-only fixups (a helper moved above a doc comment, a blank line, a doc sentence).Size
Production code grows by 37 lines net (+48 −11) for the three items, tests by 106 and docs by 6. Comment lines in
*.rsare 4 added and 2 removed: a one-line doc on the newoutput_capfield (its 0 sentinel), a one-line WHY on the 128k-tokens/hour constant (its source), and theVariantSelectdoc reworded for the new key. Whole-repo non-blank production Rust goes from 26115 (#123) to 26148 lines, and comment density from 8.75% to 8.76%. Per commit (prod net): the deadline +18 (the field on the upstream request, its eight constructors, the two engines setting it, the deadline function), the conversation key +12, the account-fault gate +4; the remaining 3 lines of the range are comment rewording counted as production lines.