Merged
Conversation
ExtractHTTPStatusCode + IsRetryableStatusCode already handle numeric status codes (500, 502, 429, 401, etc.) via regex with word boundaries. Having the same codes as bare substrings in the retryable/non-retryable pattern lists caused false positives (e.g., "processed 500 items" would match). Remove the numeric patterns and keep only the descriptive text patterns. Assisted-By: docker-agent
The only caller outside the package is ClassifyModelError itself (as a fallback path). ClassifyModelError is the intended public API and provides strictly more information (retryable + rateLimited + retryAfter). Rename to isRetryableModelError to reduce API surface. Assisted-By: docker-agent
Now that all providers (Anthropic, OpenAI, Gemini) wrap their errors in *StatusError via WrapHTTPError, the provider-specific type assertions (anthropic.Error, genai.APIError) in ExtractHTTPStatusCode are redundant. ClassifyModelError already checks *StatusError first. Replace the SDK-specific checks with a *StatusError check, keeping the regex fallback for unwrapped errors. This removes the coupling between the error classification package and provider-specific SDKs. Assisted-By: docker-agent
…tusCode The regex match is a simple integer string. strconv.Atoi is simpler, faster, and already imported. This also removes the fmt import. Assisted-By: docker-agent
math/rand/v2 is the preferred package since Go 1.22. The API is compatible (rand.Float64 exists in both). Assisted-By: docker-agent
…l vars The retryablePatterns and nonRetryablePatterns slices were local variables inside isRetryableModelError, causing a new allocation on every call. Promote them to package-level vars (like contextOverflowPatterns) so they are allocated once. Assisted-By: docker-agent
SleepWithContext and CalculateBackoff are general-purpose utilities not specific to model errors. Move them to a new pkg/backoff package for clarity and reusability. MaxRetryAfterWait moves there too. Deprecated wrapper variables are left in modelerrors to maintain backward compatibility. The runtime/fallback package now imports pkg/backoff directly. Assisted-By: docker-agent
StatusError.Error() previously delegated directly to the wrapped error, discarding the status code. This made debugging harder since log lines wouldn't reveal the HTTP status. Now Error() returns a message like "HTTP 429: rate limit exceeded" for better observability. Assisted-By: docker-agent
Callers were constructing &ContextOverflowError{Underlying: err}
directly. Adding a constructor improves encapsulation and allows
future field additions without breaking callers. Update
runtime/fallback.go to use the constructor.
Assisted-By: docker-agent
Add tests for: - ClassifyModelError with ContextOverflowError wrapping a StatusError - ExtractHTTPStatusCode with *StatusError in the error chain - FormatError with a wrapped ContextOverflowError (via fmt.Errorf) - NewContextOverflowError constructor usage - ContextOverflowError with nil Underlying (Unwrap returns nil) Assisted-By: docker-agent
Bedrock was the only provider not wrapping SDK errors in *modelerrors.StatusError. This meant ClassifyModelError had to fall back to regex parsing for Bedrock errors. Add wrapBedrockError() which extracts the HTTP status code and Retry-After header from smithyhttp.ResponseError, and call it from both the stream adapter Recv() and CreateChatCompletionStream(). Assisted-By: docker-agent
CalculateBackoff and SleepWithContext were deprecated in favor of backoff.Calculate and backoff.SleepWithContext. No callers use the modelerrors versions — all code imports pkg/backoff directly. Remove the deprecated wrappers to reduce API surface. Assisted-By: docker-agent
Only called within the package (by WrapHTTPError and tests). Rename to parseRetryAfterHeader to reduce the public API surface. Assisted-By: docker-agent
Both are only used within the package. Rename to isRetryableStatusCode and extractHTTPStatusCode to reduce the public API surface. Assisted-By: docker-agent
No callers reference modelerrors.MaxRetryAfterWait — all usage goes through backoff.MaxRetryAfterWait directly. Remove the constant and the now-unused backoff import. Assisted-By: docker-agent
There was a problem hiding this comment.
Assessment: 🟢 APPROVE
This PR refactors error handling and backoff utilities with no bugs found in the changed code.
Changes reviewed:
- ✅ New
pkg/backoffpackage with exponential backoff calculation - ✅ Bedrock error wrapping with
StatusErrorfor retry classification - ✅ Refactored
modelerrorspackage (unexported internal functions) - ✅ Updated
pkg/runtime/fallback.goto use new utilities
Verification notes:
- Nil pointer checks are correctly implemented in
wrap.go - Backoff calculations match test expectations
- Error classification logic is sound
- Resource management follows existing patterns
The code is well-tested and ready to merge.
rumpl
approved these changes
Mar 19, 2026
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.
No description provided.