fix(error-classifier): classify HTTP 408 as retryable timeout, not a non-retryable 4xx - #753
Open
hashbender wants to merge 1 commit into
Open
fix(error-classifier): classify HTTP 408 as retryable timeout, not a non-retryable 4xx#753hashbender wants to merge 1 commit into
hashbender wants to merge 1 commit into
Conversation
…non-retryable 4xx
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.
Problem
On a long session against GitHub Copilot, the chat turn would occasionally end
as a blank/empty assistant message (no content) with a visible
Error: HTTP 408: Timed out reading request body. Try again, or use a smaller request size.The turn aborted instead of recovering.Root cause
agent/error_classifier.py::_classify_by_statushad no branch for HTTP 408.It fell through to the generic tail:
But 408 is a timeout, not a permanent client error. GitHub Copilot returns
408 {'code': 'user_request_timeout', 'message': 'Timed out reading request body. ... use a smaller request size.'}when a large prompt is slow to read.Classified as non-retryable
format_error, the loop took the client-error abortpath and persisted an empty assistant turn (the blank bubble).
Fix
Add an explicit
408branch classifying all 408s as a transienttimeout(
retryable=True, notshould_compress). The loop's existing transportretry + eager-fallback-after-2-attempts path then recovers the turn.
Why NOT route it to compression (payload_too_large)
The 408 message literally says "use a smaller request size", so compressing like
a 413 is tempting — but field data argued against it. On a long copilot/opus-4.8
session the 408 was probabilistic jitter well below the hard prompt ceiling:
the same ~785k-token request that 408'd once succeeded on the very next attempt
at ~786k. Retrying the same body usually works, so auto-compaction on a 408
would silently delete conversation history for a merely transient timeout.
Genuine over-window prompts already surface as 413 /
context_overflowand keeptheir own compression path; a user can compact a 408-prone long session
deliberately via
/compress.Testing
tests/agent/test_error_classifier.py::Test408RequestTimeout):oversized-body 408 and plain 408 both →
timeout / retryable / not-compress,plus a
test_408_never_auto_compressesguard.tests/run_agent/test_408_request_timeout_loop.py): a 408 inthe real
run_conversationloop retries into a real assistant turn (no blankturn), never calls
_compress_context, and resends the same request body.format_error/non-retryable (reproducing the blank-turn regression),confirming the tests guard the symptom rather than being tautological.
Full suite green on latest
main:tests/agent/test_error_classifier.py+tests/run_agent/test_408_request_timeout_loop.py(188 passed), and the siblingtests/run_agent/test_413_compression.pyunaffected.Mirror-of: NousResearch#56932
NousResearch#56932