HCR042¶
Unsafe HTTP methods should not be hedged.
Why¶
AddStandardHedgingHandler() can send the same request to multiple endpoints at once. Hedging POST, PUT, PATCH, DELETE, or CONNECT can duplicate side effects concurrently—duplicate charges, duplicate orders—unless the operation is explicitly idempotent. This is stricter than retry: the copies overlap in flight instead of waiting for a failure.
Bad¶
services.AddHttpClient<PaymentsClient>()
.AddStandardHedgingHandler();
public sealed class PaymentsClient(HttpClient httpClient)
{
public Task<HttpResponseMessage> CreateAsync(CancellationToken cancellationToken)
{
return httpClient.PostAsync("/payments", null, cancellationToken);
}
}
Better¶
Prefer standard resilience without unsafe-method retries when the client sends non-idempotent methods:
services.AddHttpClient<PaymentsClient>()
.AddStandardResilienceHandler(options =>
{
options.Retry.DisableForUnsafeHttpMethods();
});
Or keep hedging only for a read-only client, and give unsafe methods their own named or typed client.
services.AddHttpClient<CatalogClient>()
.AddStandardHedgingHandler();
services.AddHttpClient<PaymentsClient>()
.AddStandardResilienceHandler(options =>
{
options.Retry.DisableForUnsafeHttpMethods();
});
A hedging ShouldHandle predicate that allows only safe methods also keeps this rule quiet:
services.AddHttpClient<MixedClient>()
.AddStandardHedgingHandler(options =>
{
options.Hedging.ShouldHandle = args =>
args.Outcome.Result?.RequestMessage?.Method == HttpMethod.Get;
});
Current Detection¶
The implementation reports:
- Typed-client
IServiceCollection-shaped registrations that callAddStandardHedgingHandler()when the typed-client implementation class visibly calls an unsafeHttpClientmethod anywhere in the compilation, including resolved namespace-aware matching for qualified or unqualified typed-client registration names, the implementation type inAddHttpClient<TService, TImplementation>(), and registrations split through a visibleIHttpClientBuilderlocal. - Named-client
IServiceCollection-shaped registrations such asAddHttpClient("payments")orAddHttpClient(ClientNames.Payments)when visible code in the compilation callsCreateClient("payments")or the same compile-time constant and then sends an unsafe HTTP method, including registrations split through a visibleIHttpClientBuilderlocal. Send(...)andSendAsync(...)calls when the request is visibly created with an unsafeHttpMethod, including directHttpRequestMessageconstruction, object initializers, simple local request variables that have not been reassigned before the send call, null-forgiving request and method expressions, and customHttpMethodinstances created from string literals or compile-time string constants. Unsafe methods arePOST,PUT,PATCH,DELETE, andCONNECT.
It uses the same compilation-wide unsafe-call index as HCR041, built once per compilation and only after a framework hedging (or retry) handler is paired with a typed or named client. Custom lookalike AddStandardHedgingHandler extensions, unresolved builder Services chains, and non-IServiceCollection receivers are skipped. DisableForUnsafeHttpMethods() on retry options does not suppress this diagnostic, because it does not disable hedging. A visible Hedging.ShouldHandle assignment suppresses only when it compares the request method to a safe, symbol-resolved HttpMethod constant (GET / HEAD / OPTIONS / TRACE). Constant-vs-constant checks such as HttpMethod.Get == HttpMethod.Get, lookalike HttpMethod types, and unbound ShouldHandle / HttpMethod symbols do not suppress.
Code Fix¶
For simple .AddStandardHedgingHandler() calls, the code fix replaces hedging with:
The title states the behavior change explicitly. The fix is withheld when the hedging call already has an options argument, so an existing hedging policy is never overwritten.
Suppression¶
Suppress only when the endpoint is idempotent by contract, the request uses idempotency keys, or hedging predicates exclude unsafe methods in a way the analyzer cannot model.