Repository navigation
fix(relay): probe the live socket on wake, focus and network - #2958
Conversation
After sleep the live WebSocket often dies without a close and still looks open. The app only found out at the next idle probe (60 s plus a 10 s timeout), so live updates could stop for up to ~70 s after wake with nothing shown. Wake, focus, visibility and online now also ask the live socket to answer a probe. If no probe answer arrives within 5 s, the socket is replaced through the existing reconnect path. Only a probe answer counts: other frames handled after wake may have been queued before the sleep. A probe answered in the last 15 s skips the check, a pending check is not repeated, and a relay cooldown sends nothing. Co-authored-by: ART <ea39c1c8acf57180161ef4ba4ed68abf8589f5291956b909a5aa991fc66664ca@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
kalvinnchau
left a comment
There was a problem hiding this comment.
🤖 LGTM. Probe-only proof, own deadline, cooldown/fresh skips, and teardown on reconnect/dispose all check out. Non-blocking: the 15 s fresh-answer skip also suppresses the online hint, so a network change shortly after a focus check falls back to the 60 s probe. Still needs the real lid-close test before leaving draft.
Review feedback: the 15 s fresh-answer skip also suppressed the online hint, so a socket killed by a network change shortly after a focus check waited for the 60 s probe. The online event now always probes. Co-authored-by: ART <ea39c1c8acf57180161ef4ba4ed68abf8589f5291956b909a5aa991fc66664ca@buzz.block.builderlab.xyz> Signed-off-by: Bradley Axen <baxen@squareup.com>
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a46ed12df4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (closed || !authenticated || checkDeadline || admission.delay()) | ||
| return; | ||
| if (!network && Date.now() - answeredAt < LIVE_CHECK_FRESH_MS) return; | ||
| sendProbe(); |
There was a problem hiding this comment.
Jitter wake probes before sending them
When online fires after a shared network blip, the handler in service.ts synchronously reaches this REQ, so the entire client fleet probes the relay at once and, five seconds later, can begin reconnecting in the same narrow window. Add bounded jitter before the probe while retaining the existing coalescing and capped reconnect backoff; the repository explicitly requires relay-facing recovery paths to account for fleet-wide reads and jitter their start.
AGENTS.md reference: AGENTS.md:L149-L161
Useful? React with 👍 / 👎.
| // A socket not yet admitted has its own deadline; a cooldown forbids requests. | ||
| if (closed || !authenticated || checkDeadline || admission.delay()) | ||
| return; | ||
| if (!network && Date.now() - answeredAt < LIVE_CHECK_FRESH_MS) return; |
There was a problem hiding this comment.
Invalidate probe freshness across suspension
When the machine resumes after a short sleep under WAKE_GAP_MS and focus or visibility is the only emitted hint, resume() passes false, so this accepts a probe reply from before suspension as proof that the socket is still alive. A socket can die during that suspension, leaving live updates stalled until the periodic recovery tick; wake/resume signals need to invalidate pre-suspension freshness rather than returning here.
Useful? React with 👍 / 👎.
Co-authored-by: Eva <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> * origin/main: Build sidebar headings as one fluid design-system row (#2686) fix(messages): swap bubble fills for reactions you've made, drop the ring (#2949) fix(messages): align line-start mention capsules with each other (#2899) fix(relay): probe the live socket on wake, focus and network (#2958) fix(messages): size thread avatars like the timeline's (#2890) build(desktop): share the bundled buzz CLI build across worktrees (#2894) fix(messages): render review comment mentions as chips and open at the reader's place (#2965) feat(agents): read owner messages and canvases over MCP (#2881) Signed-off-by: Tyler Longwell <tlongwell@squareup.com>
…oser (#2968) ## Summary The "Reconnecting to relay…" notice showed up too often and too loudly. It appeared 3 s after any background read failed, as a warning strip with **Retry now** above the composer, and the same failure was often repeated in the timeline and the agents banner. Most of those moments were a slow background read, not a real disconnect, and the app already retries on its own (1, 2, 4, 8, 16 s, then every 30 s forever, plus immediately on wake, focus and network-online). Now: - **One quiet notice on every page.** Relay recovery shows a single bottom-right toast reading "Buzz is having trouble connecting" with the description "Retrying automatically." It has no button and the calm `info` tone. - **It waits 30 s** (`RECOVERY_NOTICE_DELAY_MS`, up from 3 s). Blips, laptop wake and failures that the first retries fix stay silent. - **No duplicates.** The inline strip in `LiveStatus` is gone. While recovery is running, the timeline's history-edge error text stays hidden; it comes back if the failure outlasts recovery. **Retry older messages** stays either way, because recovery does not retry older history and paging stops on a failed read. - **Toasts never cover Send.** The composer and the preview's Join notice register with a new `useToastClearance(ref)` in `Toast.tsx`, and a CSS rule lifts the toast stack above them. This applies to every toast, not only this one. Pages with no composer keep the usual bottom-right spot. - **A retry that works clears its old error.** A failed `stream-retry` request used to leave its error on the live snapshot until the next stream status, so the notice could stay stuck after a later retry succeeded. `broker-live.ts` now clears only that request's own error, and leaves a newer failure in place. Taken from Taylor's #3000. Unchanged: the recovery backoff, the retry signals, `retryNow` wiring (wake/focus/online still call it), and "Live updates limited" with its Retry for failures that recovery does not retry (denials, partial roster). Decided in the Buzz thread with baxen: toast on every page, a longer grace period, simple states, no button, and a toast that keeps the composer clear. Related: #2958 (wake ping) is independent and touches different files. ## Screenshots Taken at 1440 px (light and dark) and 390 px, with a draft typed and Send clickable. They're posted in the Buzz thread where this was decided. ## Tests - Vitest: - `RecoveryNotice`: quiet until exactly 30 s, then one buttonless notice that clears once recovery settles. - `LiveStatus`: empty while recovery runs or shows; focus hands off when the notice replaces a focused Retry. - `ChannelTimeline`: the edge error text is hidden while recovering, and Retry older messages stays, in button and scroll modes. Fails without the fix. - `broker-live`: a successful retry clears its earlier control error, and keeps a newer stream failure. The case without the newer failure fails without the fix. - Browser journeys, migrated from the inline strip + Retry now to the toast. Controlled clocks jump the 30 s delay. - `relay-recovery`: quiet, then one notice, with no Retry buttons and no "Relay request failed" anywhere. Recovers with no click. - `live-status`: the notice sits above the composer and Send stays clickable at 390/800/1440 px. A long relay reason at enlarged text stays out of the notice. The notice sits above the preview's Join button. An empty preview recovers with no Retry. These layout cases **fail without the clearance CSS** (checked by removing it). - `live`: quota catch-up and the empty-roster recovery stay quiet inside the delay and recover with no click. - `focus-navigation`: the covered-composer F6 case pins clearance to 0 so that path stays exercised. - `history-loading`, `presence`, `navigation-groups`, `websocket-actions.profile`: locator updates. - Run in Chromium and WebKit: `relay-recovery`, `live-status`, `live`, `history-loading`, `presence`, `navigation-groups`, plus the toast-touching specs (`focus-navigation`, `message-actions`, `composer-attachments`, `profiles`, `reactions`, `updates`, `startup`, …). Browser cases removed: the Retry-now keyboard and focus cases in `live-status`, because the control no longer exists. The layout contract they guarded (notice never blocks the composer) is kept as the toast-above-composer assertions. ## Deferred - No human test yet in the desktop app with a real network drop. - Composers inside dialogs also lift the stack while mounted. That's harmless but untested. - `relay-recovery` failed 2 of about 60 local Chromium runs. Both times the notice cleared before its entrance finished, which suggests recovery settled while the fixture's HTTP outage was still on. I couldn't reproduce it with debug logging on a heavily loaded machine. Watch it in CI. --------- Signed-off-by: Bradley Axen <baxen@squareup.com> Co-authored-by: mbot <3754f8729004d95654c46dbab3129e4ab9ef05cc2534e2a3fbfc155983bd637b@buzz.block.builderlab.xyz> Co-authored-by: Taylor Ho <taylorkmho@gmail.com>
After the laptop sleeps, the live WebSocket often dies but still looks open. Today the app only notices at the next idle probe, which runs once a minute and then waits 10 s. So live updates can stop for up to ~70 s after you open the lid, with nothing on screen. This PR checks the socket as soon as the app wakes.
Cause
Wake, focus, visibility and network-online already call
recovery.retryNow(). That only retries reads that have already failed. A socket that looks connected isn't failed, so nothing ran until the 60 s recovery tick sent its probe.Change
LiveSubscription.check()(live.ts): an admitted socket sends the existing silence probe right away. If no probe answer comes within 5 s, it reconnects through the existingunresponsivepath, the same one the 60 s probe uses.deadline, which any frame clears.session.checkLive()forwards the check.service.tscalls it next toretryNow()from the same wake/focus/online/visibility handlers.checkand is unchanged. Desktop and web usesubscribeRelayTraffic.The backoff, the 60 s idle probe and its timeout are unchanged. Morgan vetoed changing the heartbeat timing.
Validation
live.test.ts:service.test.ts: a wake (a clock jump between wake ticks) callscheck.vitest run src/features/relay/passes: 143 files, 3212 tests.tsc --noEmitis clean.relay-wslog that a dead socket is replaced within ~5 s of wake. Draft until that's checked.Context: this is the wake fix from the connection-status investigation, rewritten on main. The original was Carl's, on Morgan's branch, which isn't on origin. It's independent of the quiet reconnect toast PR (
mbot/quiet-reconnect-toast), and the two touch no files in common.