Uh oh!
There was an error while loading. Please reload this page.
feat(mobile): keep Android connections alive in the background - #5179
feat(mobile): keep Android connections alive in the background#5179snipemanmike wants to merge 12 commits into
Conversation
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This PR introduces a substantial Android background service and headless runtime that changes connection, authentication, outbox, persistence, and resume behavior, with intentional battery, data, and notification side effects. It also adds production lint suppressions, so the cross-cutting implementation requires human review. You can add or adjust custom eligibility rules. Learn more. |
Uh oh!
There was an error while loading. Please reload this page.
81c65f7 to
e25b8e5Comparee25b8e5 to
f258f81CompareUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Reviewed the Effect-service surface of this PR (new MobileStorage operations, the imperative persistence bridge, and the touched client-runtime connection modules). The service definition order, inline Context.Service interface, make/layer exports, Foo["Service"] usage, and namespace subpath imports all follow the conventions; the Promise-based background modules stay on the imperative-adapter side and are not consumed by Effect services. One log-annotation nit noted inline.
Posted via Macroscope — Effect Service Conventions
Uh oh!
There was an error while loading. Please reload this page.
Keeps the mobile app connected to its environment while backgrounded on Android via a foreground service and headless task (upstream PR pingdotgg#5179). Carried until it lands upstream. # Conflicts: # apps/mobile/src/App.tsx # apps/mobile/src/features/settings/SettingsRouteScreen.tsx # apps/mobile/src/state/use-thread-outbox-drain.ts
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
Co-authored-by: codex <codex@users.noreply.github.com>
7b3cd80 to
c7d27a5CompareThere was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c7d27a5. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Account switches invalidated the background auth epoch and deferred explicit refreshes until the previous account's cleanup settled, but a scheduled retry or a cold headless start could still bootstrap in that window, read the new Clerk account, and publish its relay session before environment removal and the token-cache reset finished. Every bootstrap attempt now waits behind the pending sign-out cleanup and account activation, while still capturing its epoch synchronously so an invalidation issued right after it starts continues to supersede it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

What Changed
Android currently suspends the JavaScript connection runtime when T3 Code is backgrounded or the phone is locked. Reopening the app can therefore require a full reconnect and synchronization before current thread state appears.
This PR adds an Android-only, opt-in background connection mode that:
starting/runningthreadsThis does not change the WebSocket protocol, synchronization reducer, server contracts, or server behavior.
The recovery work in #5154 improves what happens after Android has suspended the client. This PR addresses the preceding problem: while the setting is enabled, it prevents Android suspension from stopping the connection runtime in the first place. The approaches remain compatible.
Why
Mobile is commonly used to monitor or continue work running on another machine through a direct connection, Tailscale, or T3 Connect. Keeping the existing runtime alive means events can arrive while the app is backgrounded, outgoing messages continue draining, and reopening can render current state immediately rather than beginning a reconnect cycle.
The feature defaults off. Android force-stop remains an unavoidable boundary, and users must launch the app once afterward.
Verification
UI Changes
Before:
After:
Interaction video (sanitized emulator)
Checklist
This is one focused Android runtime concern, but it is not a small diff; the native lifecycle, shared runtime ownership, relay authentication, resume semantics, tests, and documentation must land together to avoid partial behavior.
Implemented with GPT-5.6-sol through the Codex harness. Reviewed with Claude Fable through the Claude Code CLI.
Note
High Risk
Large cross-cutting change to Android FGS lifecycle, shared connection/outbox state, and managed-relay auth across UI unmount and account switches—regressions could affect credentials, reconnect behavior, or background message delivery.
Overview
Adds an opt-in Android “Keep connected in background” mode that keeps the existing mobile client runtime alive via a
remoteMessagingforeground service and a single React Native Headless JS task—no second WebSocket or sync stack.The new
t3-background-connectionExpo module owns native lifecycle: persistent enablement, ongoing notification, Wi‑Fi lock, boot/package-replace recovery, exponential restart backoff (only when battery optimization is ignored), and orderly stop when the user turns the feature off. JS registers the headless task at startup and coordinatesbackground-rootleases (environment catalog/shells, server configs, thread detail for the retained + active threads) plusacquireThreadOutboxDrainso queued messages still drain without a mounted UI.T3 Connect / relay gains separate UI vs background managed-relay session ownership and
backgroundManagedRelayAuth(cold Clerk bootstrap, holds during account transitions, retries).CloudAuthProviderno longer clears relay on UI unmount when background still owns the session. Returning to the app usesapplication-active-preservedwhen the native service and JS runtime are both ready, avoiding forced reconnects.Settings expose the toggle, status, and optional battery-exemption prompt; the active thread is persisted as the retained target and cleared when environments/threads are removed or caches are wiped.
Reviewed by Cursor Bugbot for commit b36b040. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add Android background connection with foreground service and shared outbox dispatcher
t3-background-connectionExpo native module with a sticky foreground service, headless JS task, broadcast receiver (boot/package-replace/restart), state manager, exponential restart backoff (1s–5min), and battery-optimization exemption supportacquireThreadOutboxDrain) that reference-counts owners, coalesces drain requests, and retries through state-backed timersapplication-active-preservedwakeup type soEnvironmentSupervisorprobes the connected session instead of reconnecting when both the native service and runtime are readyApp.tsxstartup coordinator,RootStackLayoutretained-thread tracking, and Android-only settings UI with battery-exemption promptingCloudAuthProvideraccount deactivation now invalidates background relay auth and clears both UI and background ownership; account switches suppress intermediate refresh. The outbox drain lifecycle changed from per-component to registry-scoped shared leases — multiple hook mounts share one dispatcher, and the final release stops drains, cancels retries, and unsubscribes listenersMacroscope summarized b36b040.