Repository navigation
fix(generator)!: a redirect refusal is never retried, whatever its message or wrapping - #147
Merged
Michael McQuade (giraffesyo) merged 4 commits intoOct 26, 2026
Conversation
net/http wraps every CheckRedirect error in a *url.Error, so the "stopped after" prefix check already covers ErrInsecureRedirect and ErrTooManyRedirects. The errors.Is check before it was a second path to the same answer.
…aps the SDK's or has its own message
…sage or wrapping SafeRedirects now wraps the client's redirect policy, the generated CheckRedirect or the caller's own, so each refusal comes back as a *RedirectRefusedError that unwraps to the original error. The retry declines on that type instead of on a "stopped after" message prefix, which missed a caller's refusal worded otherwise once a middleware dropped its response. A caller's policy still decides every redirect, and http.ErrUseLastResponse passes through unwrapped.
Also drops a redundant test check and keeps the reserved identifiers in order. BREAKING CHANGE: every redirect refusal, the generated CheckRedirect's or a caller's own, now comes back wrapped in a *RedirectRefusedError, and SafeRedirects always returns a copy of the client. Code that compares a refusal with ==, as in urlErr.Err == ErrInsecureRedirect, checks the result of errors.Unwrap, uses a type switch on its own CheckRedirect error, or expects SafeRedirects(c) == c must use errors.Is or errors.As instead.
Michael McQuade (giraffesyo)
marked this pull request as ready for review
October 26, 2026 03:32
Michael McQuade (giraffesyo)
deleted the
refactor/retry-redirect-prefix
branch
October 26, 2026 14:32
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.
A generated client could retry a redirect its policy had refused. The retry recognized a refusal only by an error message starting "stopped after", or by a response returned with the error. A caller's own
CheckRedirectworded otherwise, behind a middleware that drops the response, was sent again.What changes:
redirect.gogains an exportedRedirectRefusedError{Err error}withUnwrap. Its message is the refusal's own, so error text is unchanged, anderrors.Is/errors.Ason the original error still work.SafeRedirectsalways returns a copy of the client whoseCheckRedirectwraps the effective policy: the caller's own when set, otherwise the generatedCheckRedirect. The caller's policy still decides every redirect. It is wrapped, never replaced.http.ErrUseLastResponsepasses through unwrapped, because net/http compares it with==.isRetryableNetworkErrordeclines a*RedirectRefusedErrorand drops the "stopped after" prefix check.sendalso declines whenClient.Doreturns a response along with the error, which is net/http's signal for aCheckRedirectrefusal.RedirectRefusedErroris added to the reserved identifiers.Tests in
e2e_redirect_test.gocover:ErrInsecureRedirect, is tried once;*RedirectRefusedErrorwrapping the caller's error;ErrInsecureRedirectandErrTooManyRedirectsare*RedirectRefusedErrors;ErrTooManyRedirectsbehind a response-dropping middleware is not retried;ErrUseLastResponseis kept.Removing the wrap in
SafeRedirectsfails the new tests.go test ./...,go vet ./...andgofmt -l .are clean.Breaking
A redirect refusal now arrives wrapped in a
*RedirectRefusedError, andSafeRedirectsalways returns a copy of the client. Code that compares a refusal with==(such asurlErr.Err == ErrInsecureRedirect), checks whaterrors.Unwrapreturns, uses a type switch on its ownCheckRedirecterror, or expectsSafeRedirects(c) == cshould useerrors.Isorerrors.Asinstead.