Skip to content

fix(agents): reconnect a dead GraphQL socket when the app wakes - #9207

Open
404Wolf wants to merge 2 commits into
mainfrom
wolf/agent-overnight-mobile-sync
Open

404Wolf wants to merge 2 commits into
mainfrom
wolf/agent-overnight-mobile-sync

Conversation

@404Wolf

@404Wolf 404Wolf commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Why

An agent session left open on a phone overnight showed none of the night's messages when the app was opened in the morning.

Macro tasks:

  • MACRO-4328

https://macro.com/app/task/01a1884f-0fbf-7350-abf8-b3b907a5a38d

An open session follows its log over the Soup GraphQL websocket and refetches the log whenever that socket reconnects (subscribeGraphqlSoupReconnected). While the phone sleeps, the network can drop the connection without a close frame. On wake the socket still reads as open, and the client had no keepAlive, so graphql-ws never noticed it was dead. The subscription heard nothing, no reconnect happened, and the session never refetched. A session opened fresh was not affected, because its log query goes over HTTP.

How it works

foreground / online ──► ping ──► pong within 5 s ──► keep socket
                                  └─ no pong ──► terminate (4499, retried)
                                                  └─► reconnect ──► sessions resync their logs

What

  • createGraphqlSoupWebSocketLiveness (graphql-soup-websocket.ts): while a socket is connected, a return to the foreground or back online sends a protocol ping. If the server does not answer within five seconds, the client calls terminate(). Each check records its outcome (answered / terminated / closed) on a graphql_soup.websocket.liveness_check span.
  • Close 4499 (graphql-ws's "Terminated") is now retryable. Without that, a terminated socket would end every subscription instead of reconnecting.
  • createGraphqlSoupWebSocketClient (graphql-soup.ts) wires the check into the client's connected / pong / closed events.
  • Pinging only on resume, rather than a periodic keepAlive, keeps a foreground socket quiet. The socket only goes stale while the app is suspended or offline.
  • Regression test (graphql-ws-recovery.test.ts): the real graphql-ws client runs over the fake native socket. A silent server is reconnected on resume and the subscription keeps delivering; a server that answers keeps its socket. The first case fails without the check.
  • apps/web/AGENTS.md notes the behavior.

@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between c4d7d38 and 3cc83cc.

Files selected for processing: 5
  • apps/web/AGENTS.md
  • apps/web/src/lib/service-clients/service-storage/graphql-soup-websocket.ts
  • apps/web/src/lib/service-clients/service-storage/graphql-soup.test.ts
  • apps/web/src/lib/service-clients/service-storage/graphql-soup.ts
  • apps/web/src/lib/service-clients/service-storage/graphql-ws-recovery.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved GraphQL connection recovery when the app returns to the foreground or internet connectivity is restored.
    • Connections that do not respond to a health check are closed and reconnected, helping keep live updates current.
    • After reconnecting, active sessions can refresh data they may have missed while disconnected.
    • Connections that respond normally remain active.

The Soup GraphQL WebSocket client now checks liveness when the app becomes visible or connectivity returns. It sends a ping and terminates the client if no pong arrives within five seconds. Close code 4499 is retryable. The client reports connection, pong, and closure events to the liveness controller. Tests cover reconnection after an unanswered ping and continued connection after a reply.

Priority: Normal

Merge Risk: Minimal · up to 3cc83

The wake-up reconnection change appears ready to merge after normal checks; no actionable risk remains identified.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title uses the conventional commits prefix "fix(agents):" and states the main change: a dead GraphQL socket is reconnected when the app wakes. It is 63 characters, under the 72-character limit, so…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description check Passed The description states the problem (an agent session on a phone missed messages after waking) and the fix (a ping on foreground/online, a 5-second pong wait, termination with retryable close code 4499…
Full details: Title check

Explanation

The title uses the conventional commits prefix "fix(agents):" and states the main change: a dead GraphQL socket is reconnected when the app wakes. It is 63 characters, under the 72-character limit, so it meets the stated title requirements.

Full details: Description check

Explanation

The description states the problem (an agent session on a phone missed messages after waking) and the fix (a ping on foreground/online, a 5-second pong wait, termination with retryable close code 4499, and a regression test). It matches the changeset in the liveness helper, the client wiring, close-code handling, the test, and the AGENTS.md note. It is detailed and specific, so it is not vague or off-topic.

Finishing Touches: 💡 1
🛠️ Fix failing CI checks: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

A phone that sleeps with an agent session open can wake to a Soup
GraphQL websocket the network dropped without a close frame. The
socket still reads as open, so graphql-ws never reconnects it: the
session's log subscription hears nothing, and the reconnect that
makes a session refetch its log never fires. The session stays on
the transcript it had when the phone went to sleep.

When the app returns to the foreground or comes back online, ping the
connected socket and terminate it if the server does not answer
within five seconds. graphql-ws reports a terminated socket as close
4499, which is now retried, so it reconnects and every follower
refetches what it missed. Each check records its outcome on a
graphql_soup.websocket.liveness_check span.
@404Wolf
404Wolf force-pushed the wolf/agent-overnight-mobile-sync branch from 3cc83cc to ab645f2 Compare October 30, 2026 16:06
@macro-application

Copy link
Copy Markdown

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant