June 15, 2026 · 3 min read
Case Study: Persisted TanStack Query Mutations
A measured Codex run fixing a persisted TanStack Query mutation by finding the default mutation function rule.
The fixture is a React checkout flow using TanStack Query persistence.
The user places an order while offline. TanStack Query pauses the mutation. The
app persists the query cache. After reload, the app restores the cache, goes
online, and calls resumePausedMutations().
Expected result: the order is submitted once after reload.
Actual result: the order is never submitted.
Both runs used Codex GPT-5.5 against the same fixture. The prompt was:
Fix this TypeScript React fixture so `npm test` and `npm run typecheck` succeed, preserving queued checkout writes across app reloads.
The target packages were @tanstack/react-query 5.101.0 and @tanstack/react-query-persist-client 5.101.0.
Case study replay
TanStack Query persisted checkout outbox
model Codex GPT-5.5Fix this TypeScript React fixture so `npm test` and `npm run typecheck` succeed, preserving queued checkout writes across app reloads.
Without GitHits
- tokens
- 0
- time
- 0s / 126s
- Ready. Click "Watch Replay" to start.
- Reached the same mutation-default fix, but first installed dependencies and reconstructed the persistence path from local TanStack Query internals.
With GitHits
- tokens
- 0
- time
- 0s / 79s
- Ready. Click "Watch Replay" to start.
- Used TanStack Query persistence docs to register a keyed mutation default, allowing hydrated paused checkout writes to resume after reload.
Result
| Run | Time | Tokens | Tools |
|---|---|---|---|
| With GitHits | 79s | 350,965 | 19 |
| Without GitHits | 126s | 1,031,661 | 35 |
Both runs produced a passing patch. The GitHits run used 680,696 fewer processed tokens and 16 fewer tool calls.
Failure
The fixture created a QueryClient without mutation defaults:
export function createCheckoutClient(_api?: CheckoutApi): QueryClient {
return new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
}
The checkout component passed the mutation function to useMutation:
const submitOrder = useMutation({
mutationKey: submitOrderMutationKey,
mutationFn: api.submitOrder,
});
That function exists only in the mounted component. After persistence and reload, the restored mutation has its key and state. It does not have the component closure.
The relevant TanStack Query rule: a persisted paused mutation needs a default mutation function registered for its mutation key before it can resume.
Fix
Register the checkout submit function on the client:
export function createCheckoutClient(api?: CheckoutApi): QueryClient {
const client = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
if (api) {
client.setMutationDefaults(submitOrderMutationKey, {
mutationFn: api.submitOrder,
});
}
return client;
}
The test restores an offline write into a fresh client, goes online, resumes paused mutations, and checks that the checkout API receives the original order once.
Trace
The GitHits run found the rule with docs search:
search target=npm:@tanstack/react-query@5.101.0 source=docs query=persistQueryClient resumePausedMutations mutation defaults offline persistence reload limit=5
docs_read page_id=20025 start_line=1 end_line=120
docs_read page_id=20025 start_line=340 end_line=416
It then patched createCheckoutClient and ran:
npm test
npm run typecheck
The no-GitHits run installed dependencies and searched local package source for the same behavior. It read TanStack Query hydration, mutation cache, query client defaults, and default mutation option code before editing.
In this run, docs search found the package contract directly. The agent did not need to inspect source internals to discover it.