Repository navigation
fix(idempotency): delete only acquired records in the Middy onError hook - #5676
Merged
Merged
Conversation
The middleware's onError hook runs whenever an earlier step throws, including its own before hook. It deleted the record unconditionally, so a rejected duplicate removed the lock of the still-running invocation and a rejected payload mismatch removed the original completed record. Track in the Middy request whether this invocation acquired an unfinished in-progress record and delete only in that case. Closes #5675
dreamorosi
reviewed
Sep 9, 2026
dreamorosi
approved these changes
Sep 9, 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.
Summary
The
makeHandlerIdempotentmiddleware'sonErrorhook runs whenever any earlier step throws, including its ownbeforehook. It deleted the idempotency record unconditionally, so a rejected duplicate (IdempotencyAlreadyInProgressError) removed the lock of the still-running original invocation, and a rejected payload mismatch (IdempotencyValidationError) removed the original request's completed record. Either way a subsequent retry executed the handler again. Reproduced on Lambda + DynamoDB; themakeIdempotentwrapper is unaffected because itstry/catchonly wraps the function call.The middleware now tracks whether the current request acquired an unfinished in-progress record and deletes only in that case.
Changes
beforemarks the request as owning the record whenhandleMiddyBeforedid not replay a stored response, detected via the replay cleanup callback. Both per-request flags are reset at the start ofbefore, since Middy reuses a user-suppliedinternalobject across warm invocations; this also closes the same stale-flag leak in the existing.skipflag.afterreleases ownership before saving the result, so asaveSuccessfailure no longer deletes the record, in line with the wrapper.onErrorreturns early unless the request owns the record.makeIdempotent.test.tscovering the concurrent duplicate, payload mismatch, undefined stored response, laterafterfailure, sharedinternalstorage, and a laterbeforefailure that must still delete; thesaveSuccessfailure case now asserts no delete.Design note
Ownership is tracked in the middleware via
request.internal, next to the existing.skipflag, rather than as a field onIdempotencyHandler. A core-class flag would be a smaller diff (set aftersaveInProgressresolves, cleared onsaveSuccess, checked inhandleMiddyOnError), but the problem is specific to Middy's lifecycle:onErrorfires for failures inbefore, which the wrapper and decorator paths never encounter. Keeping the state in the middleware leavesIdempotencyHandleruntouched and keeps all per-request Middy state in one place.Issue number: closes #5675
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.