Skip to content

feat(mobile): keep Android connections alive in the background - #5179

Open
snipemanmike wants to merge 7 commits into
pingdotgg:mainfrom
snipemanmike:feat/android-background-connection
Open

feat(mobile): keep Android connections alive in the background#5179
snipemanmike wants to merge 7 commits into
pingdotgg:mainfrom
snipemanmike:feat/android-background-connection

Conversation

@snipemanmike

@snipemanmike snipemanmike commented Aug 1, 2026

Copy link
Copy Markdown

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:

  • runs the existing client runtime from a foreground service backed by React Native Headless JS
  • shares the existing atom registry, connection supervisors, subscriptions, and outbox dispatcher instead of creating a second WebSocket stack
  • keeps every saved environment's shell state current while retaining full detail only for the last-opened and starting/running threads
  • preserves managed-relay authentication for cold T3 Connect startup
  • probes healthy sessions on resume without forcing reconnects or resubscriptions
  • restores the service after ordinary process reclamation, task swipe-away, package replacement, and reboot after unlock
  • exposes an Android setting and the platform-required silent foreground-service notification

This 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

  • 18 focused test files passed (114 tests)
  • mobile and client-runtime typechecks passed
  • mobile lint passed
  • native module unit tests and Android release lint passed
  • arm64 production release APK built successfully, matched the expected signing certificate, and installed with replacement semantics without clearing app data
  • physical Android validation covered direct/Tailscale operation while backgrounded and locked, forced Doze, task swipe-away, Wi-Fi/cellular recovery, package replacement, and reboot restoration
  • T3 Connect ownership and cold-bootstrap paths are covered by focused tests; live T3 Connect validation was intentionally skipped
  • a clean emulator was used for UI evidence; it contains no account or personal data

UI Changes

Before:

Settings before

After:

Settings after

Interaction video (sanitized emulator)

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

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
Touches native foreground-service lifecycle, shared connection supervisors, and Clerk/relay session ownership across UI and headless contexts; mis-coordination could leak connections, double-send outbox messages, or use stale relay credentials.

Overview
Adds an opt-in Android-only “Keep connected in background” mode so the existing mobile connection runtime can keep running when the app is locked or backgrounded, instead of suspending and forcing a full reconnect on return.

The work introduces the t3-background-connection Expo module: a remoteMessaging foreground service, boot/package-replace recovery, bounded restart backoff, optional battery-optimization prompts, and a React Native Headless JS task registered at startup. The headless path reuses appAtomRegistry—one supervisor stack per environment—not a second WebSocket protocol.

Shared ownership is the main architectural change on the JS side: reference-counted leases mount the background connection root (catalog, shells, last-opened plus starting/running thread detail), acquireThreadOutboxDrain (single outbox dispatcher for UI and headless), and managedRelaySessionOwnership plus backgroundManagedRelayAuth so T3 Connect tokens can bootstrap cold while UI Clerk ownership still wins when visible.

Foreground resume behavior changes when native reports a healthy background service and runtime: a new application-active-preserved wakeup probes the current session instead of blindly reconnecting, with immediate replacement only if the probe fails. The active thread is persisted as a retained target; settings and cache/thread lifecycle paths clear it when environments or threads are removed.

User-facing surface: Settings → Background connection (Android), silent ongoing notification while enabled, and updated user/internal docs on limits (force-stop, battery cost).

Reviewed by Cursor Bugbot for commit 81c65f7. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add Android background connection service to keep relay connections alive when the app is backgrounded

  • Introduces a new Android Expo native module (T3BackgroundConnectionModule) that runs a foreground HeadlessJsTaskService with a persistent notification and a Wi-Fi high-performance lock to maintain relay connections while the app is in the background.
  • Adds a JS headless task (background-task.ts) that acquires relay auth, mounts thread outbox drain and background connection root leases, signals runtime readiness to native, and performs ordered cleanup on stop.
  • Refactors the thread outbox drain (use-thread-outbox-drain.ts) from React state/hooks into a registry-scoped acquireThreadOutboxDrain lease so it can be shared between UI and headless task contexts without double-sending.
  • Introduces managedRelaySessionOwnership and backgroundManagedRelayAuth to mediate UI vs. background relay session ownership, with UI taking precedence and background bootstrapping via Clerk with exponential backoff retry.
  • Adds a new application-active-preserved wakeup in the connection supervisor: when both native protection signals are healthy at foreground, the supervisor probes the existing connection and only reconnects immediately on probe failure, skipping normal backoff.
  • Persists the currently active thread as a retained background thread on Android (Stack.tsx) so the background service can reconnect to it; exposes settings UI via BackgroundConnectionSettingsSection including battery optimization exemption prompting.
  • Risk: The foreground service adds a persistent system notification on Android and the background task holds a Wi-Fi lock; battery and data usage may increase for users who enable the feature.

Macroscope summarized 81c65f7.

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2b5a922c-caad-4166-9a46-615c784c450d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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 added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Aug 1, 2026
Comment thread apps/mobile/src/features/background-connection/background-root.ts
Comment thread apps/mobile/src/features/cloud/backgroundManagedRelayAuth.ts
@macroscopeapp

macroscopeapp Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR introduces a substantial new Android feature (background connection support) with a new native module, foreground service, multiple new permissions, and complex background task/connection management infrastructure. Additionally, there is an unresolved review comment identifying a potential race condition in the retained-thread clearing logic.

You can customize Macroscope's approvability policy. Learn more.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 81c65f7. Configure here.

// same deleted ref was saved while this clear was pending, clear it once
// more now so the final persistence operation cannot restore it.
if (started && refsEqual(retainedThread, ref)) {
clearRetainedIfCurrent(ref);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stopped root skips pending re-clear

Medium Severity

After a pending retained-thread clear finishes, the follow-up clear only runs when started is still true. If the background root stops in between (for example disabling background connection) while navigation re-published the same deleted thread, the second clear is skipped and a queued save can persist that thread again.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 81c65f7. Configure here.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant