Conversation
Transport.RoundTrip handled exactly one 402 response: it created a credential, retried once, and returned that retry's response directly regardless of what it was. Canonical mppx (src/client/internal/Fetch.ts) loops up to maxPaymentRetries (default 3) successive actionable challenges, since a server can legitimately respond to a submitted credential with a *fresh* 402 — a different challenge, not success or a final failure (e.g. the original challenge expired mid-flight, or the credential was rejected but the server is willing to reissue). Against such a server, the Go transport recovered from zero of those retries and returned the second 402 to the caller unpaid, even though canonical mppx would have paid successfully. Wraps the existing single-attempt logic in a loop bounded by a new defaultMaxPaymentRetries = 3 constant (matching mppx's default). Each iteration re-parses WWW-Authenticate challenges from the latest response, so a fresh challenge on retry N is picked up on iteration N. The retry request is always cloned from the original req (not the previous retry), so a GetBody-backed request body is re-read fresh each attempt instead of chaining clones of clones. Behavior for a single 402 (the common case) is unchanged. A server that never accepts still gets bounded by the retry limit rather than looping forever, and a non-actionable 402 (no matching method) still returns immediately as before. Adds: - TestTransport_RoundTrip_RetriesAcrossMultiple402s: a server that rejects the first credential with a second, different challenge succeeds on the third request, and CreateCredential is called once per distinct challenge (not reusing a stale one). - TestTransport_RoundTrip_GivesUpAfterMaxRetries: a server that always returns 402 is called exactly 1+defaultMaxPaymentRetries times, then the transport returns that last 402 rather than looping forever. Ran ./pkg/client/... locally (go test -v and go vet), all tests pass including the existing suite — no regressions. Fixes tempoxyz/mpp-tools#148 (AGR-2026-052)
ygd58
force-pushed
the
fix/client-retry-multiple-402s
branch
from
August 23, 2026 14:34
1db1227 to
9eab82f
Compare
Author
|
Closing this — #140 ("align payment challenge retry behavior", merged as |
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.
What
Transport.RoundTriphandled exactly one 402 response: it created a credential, retried once, and returned that retry's response directly regardless of what it was. Canonical mppx (src/client/internal/Fetch.ts) loops up tomaxPaymentRetries(default 3) successive actionable challenges, since a server can legitimately respond to a submitted credential with a fresh 402 — a different challenge, not success or a final failure (e.g. the original challenge expired mid-flight, or the credential was rejected but the server is willing to reissue). Against such a server, the Go transport recovered from zero of those retries and returned the second 402 to the caller unpaid, even though canonical mppx would have paid successfully.Flagged by the cross-SDK audit as AGR-2026-052.
Fix
Wraps the existing single-attempt logic in a loop bounded by a new
defaultMaxPaymentRetries = 3constant (matching mppx's default). Each iteration re-parsesWWW-Authenticatechallenges from the latest response, so a fresh challenge on retry N is picked up on iteration N. The retry request is always cloned from the originalreq(not the previous retry), so aGetBody-backed request body is re-read fresh each attempt instead of chaining clones of clones.Behavior for a single 402 (the common case) is unchanged. A server that never accepts still gets bounded by the retry limit rather than looping forever, and a non-actionable 402 (no matching method) still returns immediately as before.
Testing
TestTransport_RoundTrip_RetriesAcrossMultiple402s: a server that rejects the first credential with a second, different challenge succeeds on the third request, andCreateCredentialis called once per distinct challenge.TestTransport_RoundTrip_GivesUpAfterMaxRetries: a server that always returns 402 is called exactly1+defaultMaxPaymentRetriestimes, then the transport returns that last 402 rather than looping forever.Ran
go test ./pkg/client/... -vandgo vet ./pkg/client/...locally — all pass, including the full existing suite (no regressions). Unlike the mpp-java PRs I opened today,pkg/clientonly depends on stdlib +pkg/mpp, so I could actually compile and run this one end-to-end.Fixes tempoxyz/mpp-tools#148