Repository navigation
feat(http-guzzle): add option to retry only unprocessed mutations - #143
Merged
Vincent Biret (baywet) merged 2 commits intoNov 13, 2026
Conversation
Add RetryOption::setRetryUnprocessedMutationsOnly() and a matching constructor argument. It defaults to false, which keeps the current behavior. When it is true, POST and PATCH requests are retried only after a 429 or 503 response, which indicate the server did not process the request. A 504 is returned to the caller, because the server may already have applied the request and a retry could apply it twice. Other methods are unaffected.
Jackson Smith (jackson-asmith)
requested a review
from a team
as a code owner
November 13, 2026 17:38
There was a problem hiding this comment.
🟡 Changes recommended
POST/PATCH rejection-path tests with the option enabled are missing, leaving unverified that 504 propagates the original exception without retrying while 429/503 still retry.
1 open issue
What changed in this PR
Adds opt-in retry protection to Kiota’s Guzzle middleware to reduce duplicate POST/PATCH side effects after gateway timeouts, addressing #142 while preserving existing defaults.
Changes:
- Adds
retryUnprocessedMutationsOnly, defaulting tofalse, with constructor and accessor support. - When enabled, restricts POST/PATCH retries to 429/503 responses while preserving existing retry checks and other methods’ behavior.
- Adds regression tests for defaults, methods, status sequences, per-request options, and redirects.
| File | Description |
|---|---|
packages/http/guzzle/tests/Middleware/RetryHandlerTest.php |
Adds retry-policy and redirect regression coverage. |
packages/http/guzzle/src/Middleware/RetryHandler.php |
Applies the method-specific policy during retry decisions. |
packages/http/guzzle/src/Middleware/Options/RetryOption.php |
Exposes and documents the opt-in setting and its safety assumptions. |
Review effort: Balanced
This was referenced Nov 13, 2026
…tionsOnly Add Guzzle's http_errors middleware below the retry handler so 4xx and 5xx responses arrive as BadResponseException. With the option on, a POST or PATCH 504 is raised without another request, and a 429 or 503 is still retried.
Vincent Biret (baywet)
approved these changes
Nov 13, 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.

Fixes #142
This ports microsoft/kiota-dotnet#864 to PHP. The other ports so far are microsoft/kiota-http-go#312, microsoft/kiota-python#897, microsoft/kiota-java#2251 and microsoft/kiota-typescript#2230.
Problem
RetryHandler retries 429, 503 and 504 responses whatever the HTTP method. For POST and PATCH, a retry after a 504 can apply the request twice. A 504 means a gateway stopped waiting, not that the server did nothing, so the first attempt may already have succeeded. With Microsoft Graph's sendMail, this sends the same email twice.
Callers can't avoid this today without turning off retries. The shouldRetry callback receives the delay, the attempt count and the response, but not the request, so there's no way to skip retries for some methods.
Change
This adds RetryOption::setRetryUnprocessedMutationsOnly() and getRetryUnprocessedMutationsOnly(), plus a matching optional last constructor argument, matching the .NET option.
KiotaClientFactory pushes Guzzle's redirect middleware before the retry handler, so the redirect middleware wraps it and the retry handler only sees the request for the current redirect hop. Checking the request's method is therefore always the method being resent.
Tests
RetryHandlerTest has 26 new tests covering:
The exception path is covered too: with Guzzle's http_errors middleware below the retry handler, a POST or PATCH 504 is raised without another request when the option is on, and a 429 or 503 is still retried.
With the new check removed from shouldRetry(), the 8 tests for the new behavior fail and the other 18 pass. Locally on PHP 8.5, monorepo-builder validate, PHPStan and the full PHPUnit suite (476 tests) pass.