fix(flows): reconcile the cache after a websocket reconnect

The GraphQL subscriptions are delta-only: the server registers a subscriber
for future events and never replays what it published while a client was
disconnected (and it drops events for a disconnected subscriber outright).
Nothing on the client refetched after the socket came back, so a websocket
drop during an active flow — a network blip, a laptop waking, a proxy timeout
— left a permanent hole in the streamed logs/messages/status until the user
manually reloaded the page.

Refetch the active queries on reconnect: graphql-ws hands `wasRetry` to the
`connected` handler, so on a retry (not the initial connect) call
`refetchObservableQueries()`, which re-runs the flow's queries and merges the
full current state back into the cache.

Verified live with a faithful drop (patched WebSocket, real `ws.close()` mid-
flow while the agent kept producing): before the fix the messages created
during the outage stayed missing after reconnect and only a reload recovered
them; with the fix they reappear automatically on reconnect, no reload. (A
CDP "offline" emulation does NOT reproduce this — it buffers the socket rather
than closing it, so use a real close when testing.) 1006 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Sergey Kozyrenko
2026-07-18 15:47:30 +07:00
co-authored by Claude Opus 4.8
parent c2287ad90c
commit b4b640b36b
+20 -2
View File
@@ -383,6 +383,11 @@ const replaceWithIncoming = {
};
const createApolloClient = () => {
// Holds the client for the ws `connected` handler, which is defined before the
// client exists; `lazy: true` means the socket only opens on the first
// subscription, after `.current` is set below.
const clientRef: { current?: ApolloClient } = {};
const httpLink = new HttpLink({
credentials: 'include',
uri: `${window.location.origin}${GRAPHQL_ENDPOINT}`,
@@ -393,7 +398,16 @@ const createApolloClient = () => {
lazy: true,
on: {
closed: () => Log.debug('GraphQL WebSocket closed'),
connected: () => Log.debug('GraphQL WebSocket connected'),
connected: (_socket, _payload, wasRetry) => {
Log.debug('GraphQL WebSocket connected');
// Subscriptions are delta-only — the server never replays events
// published while we were disconnected — so on a reconnect refetch
// active queries to reconcile the cache with the backend.
if (wasRetry) {
void clientRef.current?.refetchObservableQueries();
}
},
connecting: () => Log.debug('GraphQL WebSocket connecting...'),
error: (error) => {
Log.error('GraphQL WebSocket error:', error);
@@ -517,7 +531,7 @@ const createApolloClient = () => {
const link = ApolloLink.from([errorLink, subscriptionCacheLink, streamingLink, transportLink]);
return new ApolloClient({
const apolloClient = new ApolloClient({
cache,
defaultOptions: {
watchQuery: {
@@ -528,6 +542,10 @@ const createApolloClient = () => {
},
link,
});
clientRef.current = apolloClient;
return apolloClient;
};
export const client = createApolloClient();