Repository navigation
SMOODEV-3375: Method-aware retries; timeouts cancel the attempt (4.0.0) - #124
Merged
Merged
Conversation
…attempt Retries were method-blind in every port: a POST that timed out or got a 429/5xx was re-sent up to twice more. In TypeScript the per-attempt timeout (mollitia's Timeout module) only raced the request, so the losing attempt kept running server-side while the retry sent it again. Image generation (20-50s) against the 10s default billed three images and returned nothing, and any non-idempotent POST/PATCH could duplicate its side effect. Now, in all five ports, only idempotent methods (RFC 9110 9.2.2: GET, HEAD, OPTIONS, TRACE, PUT, DELETE) retry. Anything else makes exactly one attempt and surfaces its own error, unless the caller opts in with retry.allowNonIdempotent (allow_non_idempotent / AllowNonIdempotent) or the request carries a non-empty Idempotency-Key header. Eligibility is decided after pre-request hooks and the auth provider, and onRejection cannot override it. The client-side rate limiter's retry loop is unaffected: it rejects before anything is sent. TypeScript now aborts each attempt through an AbortController combined with the caller's signal, and still races the attempt so a fetch that ignores the signal times out on schedule. A caller-aborted request is no longer retried. Go waits for the cancelled attempt to unwind before retrying; Python adds a hard per-attempt deadline; Rust and .NET already cancelled and now prove it. spec/retry-idempotency-corpus.json pins the rule; every port runs it against a real local server and counts what the server received, plus a test that the timed-out attempt's connection is actually closed. Major bump: callers relying on POST retries lose them, and the new Rust RetryOptions field breaks struct literals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CfZaWemmpghhtofBauYdti
🦋 Changeset detectedLatest commit: 2c06d66 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
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.
Problem (SMOODEV-3375)
Retries ignored the HTTP method in every port. A
POSTthat timed out or got a 429/5xx was sent again, up to twice more. In TypeScript the per-attempt timeout (mollitia'sTimeoutmodule) did not stop the request it timed out, so the first attempt kept running on the server while the retry sent it again. Image generation takes 20-50s against the 10s default, so it billed three images and returned nothing. Any non-idempotent POST or PATCH (CRM writes, message sends, payments) could run its side effect more than once.Fix, the same in all five ports
retry: { allowNonIdempotent: true }(allow_non_idempotentin Python and Rust,AllowNonIdempotentin Go and .NET);Idempotency-Keyheader, in any casing.onRejectionis never called for an ineligible request.Retry-Afteron a POST is not retried unless the caller opts in.AbortControllercombined with the caller'ssignal. It still races the fetch, so an implementation that ignoressignalalso times out on schedule.asyncio.timeout.Why a major version (4.0.0)
RetryOptionsgains a field, which breaks existing struct literals. The 3.7.1 changelog explains why that must not ship as a minor.RetryOptionsnow implementsDefault, so future literals can end with..Default::default()./v4when the release runssync-versions. The READMEs already say/v4.Verification
spec/retry-idempotency-corpus.jsonhas 20 cases and a timeout-abort spec. Every port runs it against a real local server and counts the requests the server received. Each port also has a test that the connection of a timed-out attempt is actually closed.poe test(173 passed), lint, format, typecheck, buildcargo testandcargo test --all-features, clippy-D warnings, fmtgo test ./..., vet, gofmt--verify-no-changes,dotnet test -c Release(67 passed)Mutation checks. Retries were made method-blind again in each port, and the corpus tests failed:
In each port, the failures were the POST/PATCH 503, empty key, POST 429 + Retry-After, and POST timeout cases, plus the port's "ineligible POST" tests.
A second mutation made the timeout abandon the attempt instead of cancelling it. The connection-close tests then failed in TS, Rust, Go, Python and .NET.
Also in this PR
Microsoft.SourceLink.GitHub8.0.0 → 10.0.303. 8.0.0 pulls inMicrosoft.Build.Tasks.Git8.0.0, which has advisory GHSA-23fw-v26w-5fgq against it, and a clean restore now fails with NU1902.Jira: SMOODEV-3375
🤖 Generated with Claude Code
https://claude.ai/code/session_01CfZaWemmpghhtofBauYdti