Skip to content

Reconnect stale iOS shell clients after liveness failure - #7065

Closed
azooz2003-bit wants to merge 3 commits into
mainfrom
feat-ios-connection-stale
Closed

azooz2003-bit wants to merge 3 commits into
mainfrom
feat-ios-connection-stale

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Add a regression test for a saved iOS Mac connection whose render-grid liveness probe times out after the event subscription stops responding.
  • Reconnect the saved Mac on failed liveness probes instead of reusing the same persistent RPC client.
  • Make manual Retry force a reconnect when the UI already marked the Mac connection unavailable.

Verification

  • swift test --package-path Packages/iOS/CmuxMobileShell --filter MobileShellRenderGridLivenessTests
  • swift test --package-path Packages/iOS/CmuxMobileShell
  • ./scripts/reload-cloud.sh --tag ioscxn (cloud unavailable locally, local fallback succeeded)
  • ./ios/scripts/reload.sh --tag ioscxn (simulator succeeded)

Dogfood Notes

Test with the ioscxn tag. Previous bad behavior: after leaving the iOS app open for a while, sessions could stop reconnecting until the app was force-closed. Expected behavior: liveness failure or Retry rebuilds the saved-Mac client and sessions recover without restarting the iOS app.

Device reload note: ./ios/scripts/reload-cloud.sh --tag ioscxn --device-id 4A52829D-6427-599F-A166-4058881D2DF4 --wait 1200 could not run because local ASC credentials are missing. The allowed ios/scripts/reload.sh --tag ioscxn --device-only --device-id 4A52829D-6427-599F-A166-4058881D2DF4 fallback reached signing and failed because no local development team is configured.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Reconnect stale iOS shell clients when the render‑grid liveness probe fails and after network changes if the connection is marked unavailable. Recovery now rebuilds the transport and RPC client instead of reusing a wedged one, so sessions recover without restarting the app.

  • Bug Fixes

    • Added recovery planning: livenessProbe and presencePush always reconnect; networkChange reconnects when macConnectionStatus is unavailable; manual reconnects only when already unavailable or disconnected.
    • On reconnect, disconnect any live client and rebuild transport, reader, writer, and subscriptions.
    • Liveness probe failures now trigger recovery (no longer just resubscribe).
    • Healthy clients still use fast resubscribe.
  • Tests

    • Added failedLivenessProbeReconnectsSavedMacWithFreshClient and networkChangeAfterUnavailableStatusReconnectsSavedMacWithFreshClient.
    • Introduced helpers for manual reachability, attach‑ticket responses, and a reconnectable connected store.

Written for commit 13adb19. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved mobile shell recovery after connection issues, including more reliable reconnect behavior when liveness checks fail.
    • Added support for recovering from saved Mac connections during network changes and probe timeouts.
  • Bug Fixes

    • Fixed cases where recovery could stall while the app still reported an active connection.
    • Recovery now completes more consistently by refreshing the connection state before retrying.

Note

Medium Risk
Touches core iOS connection recovery and disconnect/reconnect paths; behavior change is intentional but could affect edge cases around network vs manual retry vs still-healthy transports.

Overview
Fixes iOS sessions that stayed broken until force-quit when the render-grid stream died but the persistent RPC client was wedged (e.g. after suspension).

Recovery planning in MobileShellComposite adds a livenessProbe trigger and a RecoveryPlan that chooses resync vs full reconnect from the saved Mac using macConnectionStatus and trigger type. Failed liveness probes and presence-driven recovery now rebuild transport via reconnectActiveMacIfAvailable instead of only resyncTerminalOutput. On forced reconnect, a still-“connected” client is disconnected first (disconnectLiveConnection) while preserving other Mac workspace state. Manual Retry reconnects when the Mac is already marked unavailable; network changes after unavailable also take the reconnect path.

Adds regression tests and shared test helpers (attach ticket framing, manual reachability, reconnectable connected store) plus liveness router support for mobile.attach_ticket.create.

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

@vercel

vercel Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 7, 2026 7:13pm
cmux-staging Building Building Preview, Comment Jul 7, 2026 7:13pm

@coderabbitai

coderabbitai Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Extends MobileShellComposite's recovery flow with a new .livenessProbe recovery trigger and an internal RecoveryPlan decision function choosing between resyncing a connected client or reconnecting from stored routes. Adds single-flighting of recovery, disconnect-before-reconnect handling, and new test support files with regression tests.

Changes

Liveness-probe reconnect flow

Layer / File(s) Summary
RecoveryTrigger and RecoveryPlan decision logic
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
Adds .livenessProbe trigger, RecoveryPlan/recoveryPlan(for:) to choose resync vs reconnect, single-flights recoverMobileConnection via recoveryInFlight, and disconnects a live connection before reconnecting when required.
Liveness watchdog failure path rewired
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
Render-grid liveness probe failure now calls recoverMobileConnection(trigger: .livenessProbe) instead of directly resyncing terminal output.
Test helpers for reconnectable store and attach-ticket RPC
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellReconnectRecoveryTestSupport.swift
Adds a mobile.attach_ticket.create RPC handler, LivenessManualAttachTicketResultFrame builder, ManualReachability test double, and makeReconnectableConnectedStore(...) factory.
Regression tests: reconnect after liveness probe and network change
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellReconnectRecoveryTests.swift
Adds tests verifying reconnection with a fresh client after a failed liveness probe and after a network change following an unavailable connection status.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
    participant LivenessWatchdog
    participant MobileShellComposite
    participant PairedMacStore
    participant RemoteClient

    LivenessWatchdog->>MobileShellComposite: recoverMobileConnection(trigger: .livenessProbe)
    MobileShellComposite->>MobileShellComposite: recoveryPlan(for: trigger)
    alt still connected and plan = resyncConnectedClient
        MobileShellComposite->>RemoteClient: resync and replay
    else plan = reconnectFromStoredRoutes
        MobileShellComposite->>RemoteClient: disconnect (preservingOtherMacWorkspaceState: true)
        MobileShellComposite->>PairedMacStore: fetch stored paired-Mac routes
        PairedMacStore-->>MobileShellComposite: routes
        MobileShellComposite->>RemoteClient: reconnect using fresh client
    end
Loading

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed Production changes stay on @MainActor MobileShellComposite; new mutable test doubles are actors or @unchecked Sendable with NSLock, so no new isolation debt.
Cmux Swift Blocking Runtime ✅ Passed No new blocking waits/sleeps/main-queue sync in production Swift; added NSLock and polling are confined to test-only scaffolding.
Cmux Browser Automation Off-Main ✅ Passed The PR only changes iOS CmuxMobileShell reconnect/liveness code and tests; the browser-automation rule targets TerminalController/ControlCommandExecutionPolicy, and no browser.* commands appear in...
Cmux Expensive Synchronous Load ✅ Passed PR only reroutes liveness/network recovery into existing async reconnect logic; no new synchronous agent-history or large-file load was added.
Cmux Cache Substitution Correctness ✅ Passed The diff only changes transient reconnect routing; persistence still reloads from pairedMacStore, and no fresh authoritative read was swapped for a stale cache.
Cmux No Hacky Sleeps ✅ Passed Only Swift source/test files changed; no TS/JS/shell/build runtime scripts or new fixed sleeps/polls were introduced.
Cmux Algorithmic Complexity ✅ Passed PASS: The new recovery branch is constant-time routing, and the reconnect path does one linear scan over saved Macs with Set lookups; no nested rescans or hot-path sorting were added.
Cmux Swift Concurrency ✅ Passed PASS: Recovery uses a stored/cancelled Task, and the only fire-and-forget Tasks are test cleanup. No new DispatchQueue/Combine/completion-handler patterns were added.
Cmux Swift @Concurrent ✅ Passed No new nonisolated async work or invalid @concurrent usage; the added test setup is intentionally @MainActor UI-bound.
Cmux Swift File And Package Boundaries ✅ Passed MobileShellComposite is an existing 7.7k-line oversized file, but this PR adds only a small focused recovery tweak; test-only helpers stay under Tests.
Cmux Swiftpm Lockfiles ✅ Passed Diff only changes Swift source/tests and a budget file; no .gitignore, Package.swift, Package.resolved, or Xcode project files changed.
Cmux Swift Logging ✅ Passed The patch adds no print/debugPrint/NSLog/Logger/file-stdout logging; existing mobileShellLog usage predates the PR and wasn’t changed.
Cmux User-Facing Error Privacy ✅ Passed Production diff only changes recovery routing/logging; no new user-facing error text exposes upstream/internal details, and the new strings are in tests/comments.
Cmux Full Internationalization ✅ Passed The PR only changes recovery logic, tests, and developer comments/logs; it adds no new user-facing Swift copy or catalog/localization entries.
Cmux Swiftui State Layout ✅ Passed No new SwiftUI views, GeometryReader, lazy-list store refs, or render-time state writes were introduced; changes are recovery logic plus test helpers.
Cmux Architecture Rethink ✅ Passed Production change is a small recovery-policy fix with a single recovery owner; timing/lock/polling code is confined to test helpers, which the rule allows.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Touched files are iOS shell/network logic and test fixtures only; no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes, so the rule isn’t implicated.
Cmux Source Artifacts ✅ Passed Changed paths are hand-written source/tests plus a checked-in CI budget TSV; no logs, build output, temp dirs, or other accidental artifacts appear.
Cmux No Test Or Debug Seam In Production Source ✅ Passed MobileShellComposite.swift only changed recovery planning and liveness handling; no new DEBUG/test-only accessor or seam was added in Sources, and new helpers live under Tests/.
Cmux No Ambient Global State ✅ Passed Production change adds only nested private enums and instance state on MobileShellComposite; no new file-scope API or singleton/global state.
Title check ✅ Passed The title matches the main change: reconnecting stale iOS shell clients after liveness failure.
Description check ✅ Passed The PR description covers summary and verification well, but it omits the template's demo video, review trigger, and checklist sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-connection-stale

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.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a stale iOS shell connection bug where a failed render-grid liveness probe was reusing the same wedged RPC client (via resyncTerminalOutput) instead of rebuilding the transport from the saved Mac record. It also fixes manual Retry when the connection is already marked unavailable, and closes the window where a networkChange trigger could resync a client already known dead.

  • Core fix: checkRenderGridLiveness now calls recoverMobileConnection(trigger: .livenessProbe) instead of resyncTerminalOutput directly; a new recoveryPlan(for:) function returns .reconnectStoredMac for liveness and presence-push triggers, and .resyncConnectedClient for healthy network-change and manual triggers.
  • Reconnect path: the full-reconnect Task now calls disconnectLiveConnection(preservingOtherMacWorkspaceState: true) before dialing fresh routes, tearing down the wedged transport while preserving other-Mac workspace state.
  • Guard ordering: guard !recoveryInFlight is moved before the resync fast-path so an in-flight full reconnect blocks all new triggers including resyncs.

Confidence Score: 5/5

Safe to merge. The liveness probe now correctly rebuilds the full transport stack instead of reusing a wedged client, and both new behaviors are regression-tested end-to-end.

The behavioral change is narrowly scoped: liveness-probe and unavailable-status network-change triggers now route through a full disconnect+reconnect Task that explicitly tears down the old transport before dialing fresh routes. The synchronous guard ordering prevents concurrent triggers from interfering. The two new tests exercise the exact failure modes described in the PR, and the presencePush trigger is already guarded at its only call site to fire only when the connection is already down.

No files require special attention. MobileShellComposite.swift is the only production change and is self-consistent.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Adds livenessProbe trigger case, RecoveryPlan enum, and recoveryPlan(for:) to route liveness failures through a full disconnect+reconnect; moves recoveryInFlight guard before the resync fast-path and adds disconnectLiveConnection call inside the reconnect Task body. Logic is sound.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellReconnectRecoveryTestSupport.swift New test-support file: LivenessManualAttachTicketResultFrame, ManualReachability, and makeReconnectableConnectedStore factory used by the new regression tests. All in Tests/.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellReconnectRecoveryTests.swift Two new regression tests: failedLivenessProbeReconnectsSavedMacWithFreshClient and networkChangeAfterUnavailableStatusReconnectsSavedMacWithFreshClient. Good coverage of both recovery paths.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift Adds mobile.attach_ticket.create handler to LivenessHostRouter to support the new reconnect tests. Minor comment cleanup only.
.github/swift-file-length-budget.tsv Budget line for MobileShellComposite.swift bumped from 7743 to 7771. Pre-existing over-budget file.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[recoverMobileConnection trigger] --> B{recoveryInFlight?}
    B -- yes --> Z[return drop trigger]
    B -- no --> C[recoveryPlan for trigger]
    C --> D{trigger}
    D -- livenessProbe/presencePush --> E[plan: reconnectStoredMac]
    D -- manual --> F{macConnectionStatus == .connected?}
    F -- yes --> G[plan: resyncConnectedClient]
    F -- no --> E
    D -- networkChange --> H{macConnectionStatus == .unavailable?}
    H -- yes --> E
    H -- no --> G
    G --> I{connectionState == .connected AND remoteClient != nil?}
    I -- yes --> J[resyncTerminalOutput fast path]
    E --> K{pairedMacStore == nil?}
    K -- yes --> J
    K -- no --> L[recoveryInFlight = true]
    I -- no --> L
    L --> M[Task body on MainActor]
    M --> N{connectionState == .connected AND remoteClient != nil?}
    N -- yes --> O[disconnectLiveConnection]
    N -- no --> P{connectionState != .connected?}
    O --> P
    P -- no --> Q[return defer resets flags]
    P -- yes --> R[await reconnectActiveMacIfAvailable]
    R -- success --> S[connectionState = .connected]
    R -- fail --> T[connectionRecoveryFailed = true]
    S --> U[defer: recoveryInFlight = false]
    T --> U
    Q --> U
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[recoverMobileConnection trigger] --> B{recoveryInFlight?}
    B -- yes --> Z[return drop trigger]
    B -- no --> C[recoveryPlan for trigger]
    C --> D{trigger}
    D -- livenessProbe/presencePush --> E[plan: reconnectStoredMac]
    D -- manual --> F{macConnectionStatus == .connected?}
    F -- yes --> G[plan: resyncConnectedClient]
    F -- no --> E
    D -- networkChange --> H{macConnectionStatus == .unavailable?}
    H -- yes --> E
    H -- no --> G
    G --> I{connectionState == .connected AND remoteClient != nil?}
    I -- yes --> J[resyncTerminalOutput fast path]
    E --> K{pairedMacStore == nil?}
    K -- yes --> J
    K -- no --> L[recoveryInFlight = true]
    I -- no --> L
    L --> M[Task body on MainActor]
    M --> N{connectionState == .connected AND remoteClient != nil?}
    N -- yes --> O[disconnectLiveConnection]
    N -- no --> P{connectionState != .connected?}
    O --> P
    P -- no --> Q[return defer resets flags]
    P -- yes --> R[await reconnectActiveMacIfAvailable]
    R -- success --> S[connectionState = .connected]
    R -- fail --> T[connectionRecoveryFailed = true]
    S --> U[defer: recoveryInFlight = false]
    T --> U
    Q --> U
Loading

Reviews (2): Last reviewed commit: "Tighten stale iOS recovery planning" | Re-trigger Greptile

Comment on lines 1329 to 1336
/// User-initiated reconnect from the Retry control.
public func retryMobileConnection() {
connectionRecoveryFailed = false
recoverMobileConnection(trigger: .manual)
recoverMobileConnection(
trigger: .manual,
forceReconnectConnectedClient: macConnectionStatus != .connected
)
}

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.

P2 retryMobileConnection uses macConnectionStatus as the authority for "needs full reconnect"

macConnectionStatus != .connected includes .reconnecting — the status written by markMacConnectionReconnecting() inside the resync fast-path. If a prior network-change trigger is currently mid-resync (it set macConnectionStatus = .reconnecting but did not set recoveryInFlight), a user hitting Retry will see forceReconnectConnectedClient: true, skip the resync guard, reach the guard !recoveryInFlight gate, and start a full disconnect+reconnect that supersedes the ongoing resync. Whether that is intentional or surprising depends on whether a resync under .reconnecting should be interruptible; but using macConnectionStatus as the authority here while connectionState and recoveryInFlight are the other two sources makes the intended invariant hard to see.

Rule Used: Flag correctness-critical detection/identity deriv... (source)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift`:
- Around line 235-241: The current assertion in
MobileShellRenderGridLivenessTests around pollUntil/reconnected is too weak
because it can pass on the stale client resubscribe path. Tighten the test by
asserting a reconnect-only side effect in the same repro flow, using a unique
signal like mobile.attach_ticket.create (or another request that only occurs
after rebuilding the saved-Mac RPC client) instead of just workspace.list and
macConnectionStatus. Keep the check in the existing liveness test so it fails
against the pre-fix behavior and proves recovery came from a fresh reconnect.

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift`:
- Around line 152-153: The reconnect-ticket test helper is still using real
wall-clock time instead of the injected test clock, which can make reconnect
behavior flaky. Update `manualAttachTicketResultFrame` and the
`mobile.attach_ticket.create` path in `MobileShellRenderGridLivenessTestSupport`
to accept and use the suite’s fake clock, deriving the replacement ticket’s
`expiresAt` from `clock.now` (or the injected clock source) rather than
`Date()`. Also apply the same clock threading to the related helper code
referenced by the same test support flow so all expiry and reconnect checks stay
on the virtual clock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 993c12e1-f9db-4617-a98c-f86b9919a0f9

📥 Commits

Reviewing files that changed from the base of the PR and between d268e80 and ed75854.

📒 Files selected for processing (3)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift

Comment on lines +235 to +241
let reconnected = try await pollUntil(attempts: 800) {
await router.count(of: "workspace.list") >= 2 && store.macConnectionStatus == .connected
}
#expect(
reconnected,
"a failed liveness probe must reconnect from the saved Mac record instead of reusing the stale RPC client"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Assert a reconnect-only signal here, not just “eventual recovery.”

This predicate can still pass on the old same-client resubscribe path. In this harness, only subscribe request #2 is held; later requests still succeed on the original client, so workspace.list >= 2 && macConnectionStatus == .connected does not prove that recovery rebuilt the saved-Mac RPC client. Please assert a reconnect-only side effect such as mobile.attach_ticket.create so the test actually goes red against the pre-fix behavior. As per coding guidelines, "When a user says tests missed a bug, add or adjust behavior-level coverage around the exact repro path before claiming the fix is complete."

Suggested assertion tightening
-    let reconnected = try await pollUntil(attempts: 800) {
-        await router.count(of: "workspace.list") >= 2 && store.macConnectionStatus == .connected
-    }
+    let reconnected = try await pollUntil(attempts: 800) {
+        let attachTicketRequests = await router.count(of: "mobile.attach_ticket.create")
+        let workspaceLists = await router.count(of: "workspace.list")
+        return attachTicketRequests >= 1 &&
+            workspaceLists >= 2 &&
+            store.macConnectionStatus == .connected
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let reconnected = try await pollUntil(attempts: 800) {
await router.count(of: "workspace.list") >= 2 && store.macConnectionStatus == .connected
}
#expect(
reconnected,
"a failed liveness probe must reconnect from the saved Mac record instead of reusing the stale RPC client"
)
let reconnected = try await pollUntil(attempts: 800) {
let attachTicketRequests = await router.count(of: "mobile.attach_ticket.create")
let workspaceLists = await router.count(of: "workspace.list")
return attachTicketRequests >= 1 &&
workspaceLists >= 2 &&
store.macConnectionStatus == .connected
}
`#expect`(
reconnected,
"a failed liveness probe must reconnect from the saved Mac record instead of reusing the stale RPC client"
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift`
around lines 235 - 241, The current assertion in
MobileShellRenderGridLivenessTests around pollUntil/reconnected is too weak
because it can pass on the stale client resubscribe path. Tighten the test by
asserting a reconnect-only side effect in the same repro flow, using a unique
signal like mobile.attach_ticket.create (or another request that only occurs
after rebuilding the saved-Mac RPC client) instead of just workspace.list and
macConnectionStatus. Keep the check in the existing liveness test so it fails
against the pre-fix behavior and proves recovery came from a fresh reconnect.

Source: Coding guidelines

@azooz2003-bit
azooz2003-bit force-pushed the feat-ios-connection-stale branch from ed75854 to 13adb19 Compare July 7, 2026 18:55
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 13adb19d Deployed Jul 7, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants