Skip to content

fix(query-core): release the retryer once a mutation settles - #11218

Open
iamshahid1997 wants to merge 2 commits into
TanStack:mainfrom
iamshahid1997:fix/release-settled-mutation-retryer
Open

fix(query-core): release the retryer once a mutation settles#11218
iamshahid1997 wants to merge 2 commits into
TanStack:mainfrom
iamshahid1997:fix/release-settled-mutation-retryer

Conversation

@iamshahid1997

@iamshahid1997 iamshahid1997 commented Aug 18, 2026

Copy link
Copy Markdown

Fixes #11216

🎯 Changes

Mutation.execute() never cleared #retryer after the mutation settled, so the settled retryer's promise kept that mutation's result, variables and mutationFnContext reachable for as long as the MutationCache retained the Mutation instance. This mirrors what Query.fetch() had before #11163.

This clears #retryer in the existing finally once the execution has settled, with the same identity check used for queries so that a mutation re-executed from a cache onSuccess/onSettled callback keeps its own retryer.

The continue() interaction

The issue flagged uncertainty about continue() re-entering execute() on the same instance, and it is a real hazard: releasing the retryer alone makes continue() on a settled mutation fall through to this.execute(this.state.variables!) and run the mutationFn a second time. The added test should not re-execute a settled mutation when it is continued fails with only the finally change applied.

Both internal callers (MutationCache.runNext and resumePausedMutations) filter on state.isPaused, so neither reaches a settled mutation — but continue() is public and reachable directly. The fallback is therefore gated on the mutation still being pending, which is the same condition execute() already uses to detect a restored (dehydrated) mutation, so the intended hydration path is unchanged.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Tests

Two tests in mutations.test.tsx:

  1. should not re-execute a settled mutation when it is continued — guards the continue() behavior change described above
  2. should still continue a restored paused mutation that has no retryer — covers the dehydration path the fallback exists for

Full query-core (629) and react-query (577) suites pass.

Summary by CodeRabbit

  • Bug Fixes

    • Settled mutations no longer restart when continued.
    • Retained mutation-cache entries no longer keep completed execution results, variables, or context in memory.
    • Restored paused mutations without a retryer can still continue and complete successfully.
  • Tests

    • Added coverage for settled mutation continuation and restored paused mutation behavior.

Mutation.execute() never cleared #retryer after settling, so the settled
retryer's promise kept that mutation's result, variables and context
reachable for as long as the MutationCache retained the Mutation. Mirror
the treatment Query.fetch() received in TanStack#11163, guarded by an identity
check so a mutation re-executed from a cache callback keeps its own
retryer.

Releasing the retryer alone would change continue(): a settled mutation
no longer has a retryer to continue, so it would fall through to
execute() and run the mutationFn a second time. Both internal callers
filter on state.isPaused and never reach a settled mutation, but
continue() is reachable directly, so the fallback is now gated on the
mutation still being pending -- the same condition execute() already uses
to detect a restored mutation.

Fixes TanStack#11216
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8deecef0-de64-4653-89ac-9cb89af3a5e2

📥 Commits

Reviewing files that changed from the base of the PR and between 279bb55 and 8128bfc.

📒 Files selected for processing (3)
  • .changeset/release-settled-mutation-retryer.md
  • packages/query-core/src/__tests__/mutations.test.tsx
  • packages/query-core/src/mutation.ts

Included review availability: Your plan includes up to 10 reviews per rolling hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

The mutation lifecycle now releases settled retryers and avoids rerunning settled mutations. Tests cover settled continuation and restored paused mutations without retryers. A patch changeset documents the memory-retention fix.

Changes

Mutation retryer lifecycle

Layer / File(s) Summary
Mutation execution lifecycle
packages/query-core/src/mutation.ts, .changeset/release-settled-mutation-retryer.md
continue() only executes pending mutations. execute() uses a local retryer and clears the instance reference after settlement. The changeset marks a patch release.
Mutation continuation coverage
packages/query-core/src/__tests__/mutations.test.tsx
Tests verify that settled mutations do not rerun and restored paused mutations without retryers complete successfully.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 8128b

The change releases settled mutation retry state while preserving paused-mutation continuation behavior, with focused regression coverage and passing test suites; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Mutation
  participant Retryer
  participant MutationCache
  Mutation->>Mutation: continue() checks mutation status
  Mutation->>Retryer: start mutation execution
  Retryer-->>Mutation: settle execution promise
  Mutation->>Mutation: clear current retryer reference
  Mutation->>MutationCache: runNext(this)
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes release settled retryers with an identity check and preserve pending restored-mutation continuation, satisfying issue #11216.
Out of Scope Changes check ✅ Passed The code and tests directly support retryer release and safe mutation continuation, with no unrelated changes identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly and concisely describes the primary change: releasing the retryer after a mutation settles.
Description check ✅ Passed The description includes the required Changes, Checklist, and Release Impact sections, with motivation, tests, and changeset details.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@TkDodo TkDodo left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the tests here also pass on main, which means they don’t test what they claim to fix. please add better tests that actually show that this code fixes something!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mutation.execute() never releases #retryer after settling, unlike Query.fetch() after #11163

2 participants