fix: replace the backend health poller with a connectivity state machine (no more false Server Unreachable during recordings)#126
Merged
Conversation
…a passive-first connectivity state machine that derives reachability from real request outcomes (via the axios interceptor), browser online/offline and visibility events, and demotes active probing to an idle fallback, eliminating false Server Unreachable alerts when a backgrounded tab is throttled during a recording
…EnforceCanonicalHttpsMiddleware so container health checks and plain-HTTP uptime monitors get a clean 200 instead of a canonical-HTTPS redirect, and repoint the middleware tests at a guarded path
… HTTPS target to the plain-HTTP port ($server_port resolves to 80 on the listen 80 block), producing a malformed https://$host:80 location; redirect to standard HTTPS via $host so plain-HTTP health checks and uptime monitors get a well-formed target
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.
Pull Request
Description
Replaces the frontend backend-health poller with a connectivity state machine, eliminating the false "Server Unreachable" alerts that fire during meeting recordings.
The bug
"Server Unreachable" was raised while the backend was fully healthy (verified from container logs:
nojoin-apihad zero restarts and served/api/v1/system/health200 throughout the incident). The old poller (frontend/src/lib/serviceStatusStore.ts) committed a category error: it equated "a synthetic health probe failed" with "the backend is down". During a recording the Nojoin tab is backgrounded, the browser throttles its timers (the 60s-cadence backup poll in the logs is the tell-tale), and on the hidden to visible transition the wake-up probes time out client-side within the 5s window before ever reaching the server. Three aborts in ~7s trippedbackendFailCount > 2and raised the alert. #113 only guarded thevisibilityState === "hidden"case, not the wake-up burst.The architecture
Reachability is now derived from observed request outcomes, not from a parallel synthetic probe channel:
frontend/src/lib/api/client.ts). Its response interceptor now classifies each outcome by failure mode and feeds the monitor: any HTTP response (even 4xx/5xx) proves the server answered; only a missing response or a 502/503/504 gateway error indicates unreachability. During a recording, segment uploads (which route through the same client) continuously prove reachability, so the false alarm is now structurally impossible.connectivity/reducer.ts) with explicit statesonline | checking | offline | unreachable. Status is a derived function of(browserOnline, freshness of last success, probe-failure streak). It is a pure(state, event) -> statereducer, exhaustively unit-tested without timers or network.online/offlineevents give client-offline its own distinct state and message ("You're offline") instead of blaming the backend.connectivity/monitor.ts). A dedicated liveness probe runs only when real traffic has not proven reachability recently; escalation tounreachablerequires the browser to be online AND no success for a staleness window AND repeated dedicated-probe failures. A real request failure forces a prompt verification probe so genuine mid-session outages are still caught quickly.serviceStatusStoreis slimmed to health content only (db, worker, version, deployment warnings); its failure to load can no longer assert "unreachable".MeetingControlsreads reachability from the connectivity store.Backend cleanup (included)
Exempts
/healthand/api/healthfromEnforceCanonicalHttpsMiddleware(backend/main.py). Liveness endpoints are called by container/orchestrator health checks and internal uptime monitors over plain HTTP without forwarded-proto headers; they were being 307-redirected to the canonical HTTPS origin (log noise, and a broken plain-HTTP monitor). They now answer a clean 200. The three middleware tests that used/api/healthas their probe were repointed at a guarded path so they still exercise the redirect/reject/forwarded-proto logic.New dependencies: none.
Type of change
Checks run
pytest backend/tests865 passed locally.ruff checkandruff format --checkon the changed backend files, both clean.cd frontend && npm run lintclean.cd frontend && npm run test199 passed (39 files), including 21 new connectivity reducer/driver tests.cd frontend && npm run buildsucceeded.Migration impact
Documentation impact
Security impact
EnforceCanonicalHttpsMiddlewarenow exempts only the two liveness endpoints (which already returned a static{"status": "ok"}and carry no sensitive data). HTTPS enforcement andTrustedHostMiddleware(which runs first) are unchanged for every other path; the redirect, reject, and forwarded-proto tests were repointed at a guarded endpoint and still pass.docs/SECURITY.mdboundaries preserved.Manual verification
Reachability is now driven by real traffic, so the key manual check is a recording: start a meeting capture, background the tab (switch to the meeting app) for a few minutes, and confirm no false "Server Unreachable" toast appears while segment uploads continue. Additional checks: toggle the OS network off to confirm the distinct "You're offline" message; stop the backend container mid-session to confirm a genuine outage still surfaces "Server Unreachable" within ~15-30s and clears on recovery; over plain HTTP,
curl http://<host>/api/healthnow returns 200 rather than a redirect.