Skip to content

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

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

feat(mobile): keep Android connections alive in the background#5179
snipemanmike wants to merge 12 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
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 remoteMessaging foreground service and a single React Native Headless JS task—no second WebSocket or sync stack.

The new t3-background-connection Expo 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 coordinates background-root leases (environment catalog/shells, server configs, thread detail for the retained + active threads) plus acquireThreadOutboxDrain so 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). CloudAuthProvider no longer clears relay on UI unmount when background still owns the session. Returning to the app uses application-active-preserved when 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

  • Adds the t3-background-connection Expo 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 support
  • Adds JS background infrastructure: background task lifecycle, retained-thread persistence (single-flight load, serialized saves/clears), target selection, background relay-auth bootstrap with epoch invalidation and transition holds, and lease-based UI/background managed-relay session ownership
  • Refactors the thread outbox drain from React hooks to a registry-backed shared dispatcher (acquireThreadOutboxDrain) that reference-counts owners, coalesces drain requests, and retries through state-backed timers
  • Adds application-active-preserved wakeup type so EnvironmentSupervisor probes the connected session instead of reconnecting when both the native service and runtime are ready
  • Integrates into the app entrypoint, App.tsx startup coordinator, RootStackLayout retained-thread tracking, and Android-only settings UI with battery-exemption prompting
  • Risk: CloudAuthProvider account 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 listeners

Macroscope summarized b36b040.

@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: 6f807a1b-4f6d-42c6-a833-ef045a6e163d

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: 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.

Comment thread apps/mobile/src/features/background-connection/background-root.ts
@snipemanmike
snipemanmike force-pushed the feat/android-background-connection branch from 81c65f7 to e25b8e5 Compare August 6, 2026 03:27
@juliusmarminge
juliusmarminge force-pushed the feat/android-background-connection branch from e25b8e5 to f258f81 Compare August 10, 2026 10:58
Comment thread apps/mobile/src/features/background-connection/retained-thread.ts

@macroscopeapp macroscopeapp 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.

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

Comment thread apps/mobile/src/persistence/mobile-storage.ts Outdated
camerontaylor added a commit to camerontaylor/t3code that referenced this pull request Aug 18, 2026
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
snipemanmike and others added 11 commits September 1, 2026 19:42
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>
@snipemanmike
snipemanmike force-pushed the feat/android-background-connection branch from 7b3cd80 to c7d27a5 Compare September 2, 2026 02:54

@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.

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.

Comment thread apps/mobile/src/features/cloud/backgroundManagedRelayAuth.ts
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>
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.

2 participants