Keep daemon reconnect running while the app is backgrounded - #4444
Merged
Merged
Conversation
Visibility should flush cached state and accelerate foreground recovery without disabling normal reconnect backoff. Preserve healthy sockets and retry whenever the OS permits execution.
Contributor
|
| Filename | Overview |
|---|---|
| packages/app/src/runtime/host-runtime.ts | Removes visibility-gated reconnect state so all clients retain normal retry behavior while foregrounding still requests an immediate connection. |
| packages/app/src/runtime/host-runtime.test.ts | Expands lifecycle coverage across all initial app states, multiple hosts, healthy sockets, and foreground reconnection. |
| packages/app/src/navigation/host-runtime-bootstrap.test.ts | Verifies initial visibility and forwarding of active, inactive, and background lifecycle transitions. |
| packages/app/e2e/browser/background-reconnect.spec.ts | Exercises hidden-state reconnection against a restarted isolated daemon and verifies the connection survives refocus. |
| packages/app/e2e/browser/agent-stream-ui.spec.ts | Waits for stable assistant output before measuring scrollability, avoiding temporary reasoning-row height. |
Sequence Diagram
sequenceDiagram
participant OS as App lifecycle
participant Store as HostRuntimeStore
participant Client as Daemon client
participant Daemon as Daemon
OS->>Store: inactive/background
Store->>Store: flush replica cache
Note over Client: Normal reconnect backoff remains enabled
Daemon--xClient: Connection drops
Client->>Daemon: Retry while process can execute
OS->>Store: active
Store->>Client: ensureConnected()
Client->>Daemon: Immediate reconnect if needed
Reviews (5): Last reviewed commit: "chore(app): keep reconnect QA evidence i..." | Re-trigger Greptile
12 tasks
omorsi45
pushed a commit
to omorsi45/paseo
that referenced
this pull request
Sep 8, 2026
…#4444) * fix(app): keep reconnect enabled while backgrounded Visibility should flush cached state and accelerate foreground recovery without disabling normal reconnect backoff. Preserve healthy sockets and retry whenever the OS permits execution. * test(app): clarify the background reconnect journey * test(app): name reconnect screenshot helper inputs * test(app): wait for stable content before delayed-history scroll * chore(app): keep reconnect QA evidence in the PR body
Legoless
added a commit
to Legoless/paseo
that referenced
this pull request
Sep 9, 2026
Port of getpaseo#4160 and getpaseo#4444. Flush the replica cache when hidden, reconnect every host immediately on resume, and keep daemon reconnect backoff running while backgrounded. Disconnect no longer clears turn liveness so queued messages still drain after a backgrounded turn finishes.
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.
Linked issue
Refs #4160, which introduced this regression.
Type of change
Reasoning
After hiding a browser tab, minimizing a desktop window, or leaving the mobile app, a dropped daemon connection could remain offline because app visibility disabled automatic reconnect. Keep normal reconnect backoff enabled whenever the OS lets the process run, and retain #4160's immediate foreground reconnect and healthy-socket preservation.
The fix removes visibility-controlled reconnect state from the host runtime. Visibility still flushes the replica cache on departure and calls
ensureConnectedAll()on return. No new state machine or platform branch is needed.getIsAppVisible()also returns false for nativeinactive, so substituting that helper would not fix the retry policy.Goals
Non-goals
QA
Verified on 2026-09-07 against the worktree app and isolated development daemon (both report 0.7.2). No wire changes. The baseline was c46aee4, including #4160.
Regression
The lifecycle regression fails before the fix:
inactivechanges the client's reconnect flag tofalse. With the fix, 100 tests pass across the runtime and bootstrap files, covering all three initial states, two hosts, healthy sockets, and immediate foreground reconnect.cd packages/app npx vitest run src/runtime/host-runtime.test.ts src/navigation/host-runtime-bootstrap.test.ts --bail=1Key failing-first output:
Web
The committed Playwright journey starts its own daemon, seeds only that host, dispatches hidden visibility, terminates/restarts its own daemon process tree, waits for a new socket and the Online badge while still hidden, then refocuses.
The same journey failed against the baseline: no new WebSocket for 30 seconds after daemon restart. It passed once during development and twice consecutively after restoring the fix. A further run passed after moving setup and cleanup into a test fixture (1 passed, 34.3s). Key command output:
Android
Built the dev client from this worktree. The already-running emulator was reserved, so this run used a second existing API 35 x86_64 AVD with fresh app data. The daemon used
startIsolatedHostDaemonwith its own temporary home; the OS assigned daemon port 42049 and Metro port 33799. Restart called the helper'srestart(), which only terminates its captured child process tree. These port values belong to this run; allocate fresh ports when repeating it.Dismissed the dev menu and first-launch permission prompts. Opened Settings → Overview and verified
Online,10.0.2.2:42049.Observed resume handshake: 471 ms from Android's resume callback to the daemon accepting the connection. The first post-return assertion passed; there was no persistent disconnected screen. The backgrounded Android process did not complete a reconnect before return in this run. The same socket remained attached across the subsequent app-switcher round trip (no additional
hello).Raw event/log excerpts (same machine clock; opaque identifiers omitted):
Android's installed React Native
AppStateModule.ktemitsbackground/active, not literalinactive. The unit regression exercisesinactive; real iOS interruption behavior remains unverified on this Linux host.Electron Linux
Built production main with
npm run build:main --workspace=@getpaseo/desktop. A temporary Playwright_electron.launchharness launchedpackages/desktopunderxvfb-run -a, with separate Electron user data, daemon management disabled,PASEO_HOMEpointing at the owned isolated daemon, andEXPO_DEV_URL=http://localhost:33799.Used
app.browserWindow(page)to hide the actual app window, restarted the isolated daemon, then read the host's Online badge before showing the window. Electron 44.2.0 reconnected at 20:18:03.817 after the restart completed at 20:18:01.050. Captured the screenshot after showing the window again.Under Xvfb, Chromium continued reporting document visibility as
visibleeven though the native window was hidden. This verifies actual window hiding and reconnect; the browser test separately proves the hidden document lifecycle. This used the development wrapper and shared Metro renderer, not a packaged release or macOS/Windows window manager.Checks and limits
npm run typecheck,npm run lint,npm run format, andgit diff --checkpass. Lint: 0 warnings, 0 errors.This does not verify silent half-open sockets, mobile execution after OS suspension, relay outages, or notification delivery. Healthy sockets and the existing foreground fast path remain unchanged.
CI follow-up
The first complete CI run passed the new reconnect test on its first attempt (19.0s), but failed the existing delayed-history scroll journey on both attempts. Its trace showed temporary reasoning rows satisfying the scroll-height prerequisite, then collapsing before the wheel gesture. The test now waits for the mock's next assistant response before measuring scrollable height. Scroll-distance and viewport-preservation assertions are unchanged.
Evidence cleanup is complete in one commit and one push:
6e56bc85db6902b89fc8916913a4d2f0e81d99d1. QA artifacts and their links are removed; the unit tests and real Playwright spec are unchanged. The commands, key output, timing evidence, and platform limits are inline above.The cleanup head is green: CI, Nix Linux/macOS builds, Docker, and Greptile review all passed.
gh pr checks 4444 --watchexited 0; no unresolved review threads. No workflow reruns or additional pushes were needed. The reconnect journey passed on its first attempt (18.8s), and the corrected delayed-history journey passed on its first attempt (20.0s). Two existing tests passed on retry: command-center workspace actions and sidebar row shape.Review follow-up
document.visibilityStateand dispatchingvisibilitychange. The test exercises the real app, WebSocket, and restarted daemon and fails on the original runtime. It proves the application retry policy; it does not claim to simulate Chromium timer throttling or OS suspension. Actual native window hiding is separately documented above.Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatpasses