Repository navigation
Description
Describe the bug
QueryClient.clear() empties both caches, but garbage-collection timers can outlive the entries they were meant to remove:
- MutationCache.clear()/remove() remove a mutation without clearing its existing GC timer.
- QueryCache.remove() destroys a query and clears its timer, but an in-flight fetch cancellation later runs its finally block and schedules GC again. Removing the last observer after removal can also recreate a timer.
In the reproductions below, the residual timer retains the entry for gcTime (five minutes with browser defaults). An unbounded memory leak has not been measured. A removed mutation that is still pending can repeatedly schedule GC until it settles. The caches are empty; the timers are still live.
Your minimal, reproducible example
Save the file in an empty directory, then run:
npm install --no-save @tanstack/query-core@5.107.1
node reproduction.mjsIt uses @tanstack/query-core 5.107.1 and its public timeout provider to count live timers without waiting for them. The observer/query classes are real; no request, framework app, or private source is required. Regressions in packages/query-core/src/tests/cacheGc.test.tsx exercise the same behavior with the upstream test runner.
Steps to reproduce
- Complete a mutation, reset its observer so it is unused, then call client.clear(). Its cache is empty but a 300000 ms timer remains.
- Start a pending query with an observer that consumes its AbortSignal, then clear the client. The signal aborts and the cache empties, but once cancellation settles another 300000 ms GC timer appears. Later observer removal can also schedule it again.
- Explicitly destroying the removed entries after these continuations clears the residual timers.
Expected behavior
Removing an entry should release its existing GC timer and should not let later cancellation or observer cleanup schedule another timer for that removed entry. A replacement query with the same key must retain its own normal GC lifecycle.
This does not require cancelling running mutations, changing gcTime defaults, or disabling server-side GC. Cached entries, reset/refetch and entries re-added and used through their normal observer lifecycle should still be collected normally.
How often does this bug happen?
Every time in the isolated reproductions.
Platform
macOS arm64. The library regression tests run on Node.js 24.22.1 using the repository's jsdom environment; the standalone reproduction counts timers with a custom TimeoutProvider rather than a browser heap snapshot.
TanStack Query adapter and version
Core/vanilla reproduction; our consumer uses react-query. @tanstack/query-core and @tanstack/react-query 5.107.1. Also reproduced on the current main checkout, commit 8f6da1e. TypeScript 6.0.3 for the local checkout.
Additional context
Our test teardown currently retains references to cache entries even after inline clear(), awaits query cancellation, then explicitly destroys queries and mutations. Releasing removed entries' timers in the library would let us simplify that cleanup. No application-level latency or memory improvement has been measured.
Related: #12025 and closed/unmerged #12053 proposed disabling GC timers on the server. This report is narrower: release timers for entries explicitly removed from a cache, while keeping normal finite gcTime behavior on both client and server.
A fix is prepared with 14 regression cases. The complete query-core target passes 1,654 tests, and the repository test:pr gate passes all 627 selected tasks across 194 projects (24 valid Nx cache hits). The patch includes a changeset.
Activity