Fix Apollo store-reset race when a scan/clean task completes - #7093
Draft
ordureconnoisseur wants to merge 2 commits into
Draft
Fix Apollo store-reset race when a scan/clean task completes#7093ordureconnoisseur wants to merge 2 commits into
ordureconnoisseur wants to merge 2 commits into
Conversation
ordureconnoisseur
marked this pull request as draft
July 1, 2026 10:29
ordureconnoisseur
force-pushed
the
fix-resetstore-race-condition
branch
from
July 1, 2026 10:34
9564588 to
7d2df41
Compare
client.resetStore() is cache.reset() + reFetchObservableQueries(), but the cache.reset() step also cancels any query still in flight, rejecting it with "Store reset while query was in flight". If a scan or clean task finishes while a list page is loading, this briefly flashes an "Error loading items" screen (Apollo error code 42) before resetStore's own follow-up refetch lands and the list corrects itself. Call cache.reset() directly instead (the same method clearStore() uses internally), then reFetchObservableQueries(). This keeps the same full-cache-wipe behavior resetStore() had, so no stale data lingers for screens the user isn't currently on, without the in-flight cancellation that causes the flash. Tradeoff: this skips cancelPendingFetches, a general Apollo safety net against custom merge functions assuming stale cache state. Not exploitable in this codebase today (no custom incremental merge functions in typePolicies, and plugins can't add any), but worth flagging as a real, if currently theoretical, cost of this approach.
ordureconnoisseur
force-pushed
the
fix-resetstore-race-condition
branch
from
July 1, 2026 10:54
7d2df41 to
d6b0b7e
Compare
ordureconnoisseur
marked this pull request as ready for review
July 17, 2026 11:04
ordureconnoisseur
marked this pull request as draft
July 17, 2026 11:11
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.
Description
createClient.tscallsclient.resetStore()when the scan/clean-complete subscription fires.resetStore()iscache.reset()+reFetchObservableQueries(), but thecache.reset()step also cancels any query still in flight, rejecting it with "store reset while query was in flight" (Apollo error code 42). If a scan finishes while a list page is loading, this flashes an "Error loading items" screen for about a second until resetStore's own follow-up refetch lands and the list corrects itself.Fix: keep calling
client.resetStore()unchanged (full Apollo semantics, including its in-flight cancellation safety net), but only once no watched query is loading. When the subscription fires, the client checks its active queries (via the public, non-deprecatedgetObservableQueries()andgetCurrentResult()) every 100ms and resets once nothing is in flight. The idle check and theresetStore()call run back to back in the same task; the only remaining gap is the single microtaskresetStore()itself defers by internally (Promise.resolve().then(...)) before cancelling anything. A watched query can only start from a task (React renders and schedules in tasks), so in practice nothing can slip in between; a query started imperatively from a promise continuation in that exact microtask would get today's cancellation behavior, nothing worse.Two bounds keep this safe:
resetStore()calls, which could previously cancel each other's refetches and cause the same flash.Two scope notes:
cache.reset({ discardWatches: false })+reFetchObservableQueries()directly, which skips Apollo'scancelPendingFetchessafety net and left a residual risk if a custom incremental merge function is ever added totypePolicies. This version has no such tradeoff;resetStore()is used as-is.client.query()in flight at reset time gets the same cancellation it gets today; the flash bug specifically comes from watched list queries, which this covers.Related Issue
Same stack trace as #6458 (closed, root cause not found there, reporter's actual fix was removing a plugin that triggered scans more often).
Testing
pnpm run check(tsc),biome lint,biome formatall pass.@apollo/client3.14 itself (the version in this repo), using a link with controllable response delay and a watched query simulating a mounted list page:resetStore()mid-flight reproduces the identical error (message 42).resetStore(): a watched query that resolved and was then unsubscribed (simulating navigating away from a list) has its cache entry wiped by the reset, confirmed viacache.extract().Screenshots
No visual change.
Checklist
AI Usage Disclosure
Used Claude Code to investigate the error (decoded the Apollo error link to error code 42, traced it to the
resetStore()call increateClient.ts) and to explore fix options. An earlier revision replacedresetStore()with a directcache.reset(), which traded away Apollo's in-flight cancellation safety net; I rejected that tradeoff, and this revision (wait for watched queries to settle, then callresetStore()unchanged) came out of that iteration. The behavioral tests in the Testing section were run with Claude Code against my own instance's dependency tree. I reviewed the diagnosis, the approach, and the diff, and take responsibility for the change.