Skip to content

Bound iOS pairing attempts - #6495

Merged
lawrencecchen merged 35 commits into
mainfrom
feat-ios-pairing-timeout
Jun 22, 2026
Merged

lawrencecchen merged 35 commits into
mainfrom
feat-ios-pairing-timeout

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • make RPC request timeouts return without waiting for stuck transport cancellation to unwind
  • cap one iOS pairing attempt to a single 8s budget across QR, pasted QR, and manual host connect
  • dismiss the QR scanner sheet before handing scanned content into the shared pairing path

Tests

  • swift test --package-path Packages/iOS/CmuxMobileShell
  • swift test --package-path Packages/iOS/CmuxMobileRPC
  • swift test --package-path Packages/iOS/CmuxMobileShellUI (blocked: local GhosttyKit.xcframework binary artifact is absent)

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


Note

High Risk
Large changes to authentication lifecycle, token races, and sign-out—security-critical paths—with substantial new concurrency/timeout behavior on mobile RPC pairing.

Overview
iOS pairing & RPC — User-initiated pairing is capped by a single 8s attempt budget (default on MobileSyncRuntime); per-RPC timeouts clip to whatever time remains. MobileCoreRPCClient uses one RPCRequestDeadline for Stack token fetch/force-refresh, connect, and send, with RPCStackTokenGate deduping and briefly blocking retries after timeouts. MobileRPCConnectAttemptRegistry and abandoned-connect cleanup close slow or superseded connects without blocking later attempts; host-status probes can attach a cached token via stackAccessTokenForStatusProvider.

Auth — Launch CMUXAuthState.primed treats a cached user with known tokens as signed in immediately (validation continues in the background) and adds a shorter sessionRestore timeout (8s) so restore fails open instead of sticking on “Restoring session”. AuthCoordinator gains coordinator-owned phases for credential exchange, validation, token-touching calls, and post-sign-in hooks, plus sign-in preflight that cancels/waits on stale token work; sign-out coordinates credential capture, cancels in-flight work, and clears late token writes. storedAccessToken() is added on TokenProviding for non-mutating reads.

CLI & CI — New cmux sessions / session-debug reads ~/.cmuxterm/*-hook-sessions.json with filters and optional Codex index/transcript hints (no live socket). CI runs CmuxAuthRuntime Swift Testing suites individually via --filter.

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


Summary by cubic

Bounds iOS pairing to a single 8s attempt and tightens auth/RPC deadlines so timeouts fail fast without piling up connects or token fetches. Launch authenticates cached users immediately and adds a cmux sessions list CLI for session-restore diagnostics with localized help.

  • New Features

    • Pairing: one 8s attempt budget across QR, pasted QR, and manual host connect; per-request timeouts clip to the remaining budget; per-route gating via MobileRPCConnectAttemptRegistry with one bounded retry and a brief hard-gate/reset if late close hangs; stored‑Mac reconnects reuse an in‑flight attempt.
    • Auth: 8s sessionRestore; a cached user with stored tokens is treated as signed in while validation runs; token read/refresh, credential exchange, validation, and post‑sign‑in hooks are bounded and tracked; sign‑in preflight cancels/awaits stale token work; dev auto‑login is skipped when tokens exist; adds non‑mutating storedAccessToken().
    • RPC: one request deadline covers Stack token acquisition/force‑refresh, connect, and send; RPCStackTokenGate dedupes normal and force‑refresh token fetches and briefly suppresses retries after timeouts; slow or preinstalled connects are closed before sending; request‑queue tombstones are fixed and connect retry is allowed after a timeout; host‑status probes use a cached token via stackAccessTokenForStatusProvider.
    • CLI: cmux sessions list (alias session-debug) lists/filters saved agent hook sessions from ~/.cmuxterm with optional Codex transcript/index hints; localized usage/help.
  • Bug Fixes

    • Scanner sheet dismisses before handing QR to pairing; session‑restore timeouts preserve the cached session instead of leaving the app on “Restoring session.”
    • Timed‑out/cancelled RPCs return requestTimedOut and then tear down connects without blocking other waiters; timed‑out Stack token acquisition maps to a retryable requestTimedOut in CMUXMobileRuntime.
    • Sign‑out cancels in‑flight credential exchanges/validations and drops late token writes so stale sessions cannot resurrect.
    • SystemDeliveredNotificationClearer no‑ops outside the app bundle; stabilize tests and deflake CmuxAuthRuntime CI by running Swift Testing suites individually.

Written for commit 810f2f7. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added a configurable pairing-attempt deadline that bounds the full QR and manual pairing flow (with a sensible default).
  • Improvements
    • Pairing request timeouts are now strictly budgeted against the overall deadline for quicker, more consistent failures.
    • Cached-session/auth flows authenticate immediately when cached tokens are available and use phase-appropriate timeouts (including a session-restore deadline).
    • The pairing scanner sheet dismisses immediately after a successful scan.
    • RPC request timeouts now close slow connections before stale authenticated requests are sent.
  • Tests
    • Added/expanded coverage for pairing-attempt deadlines, session-restore timeout behavior, and RPC timeout connection closing.

@vercel

vercel Bot commented Jun 20, 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 Jun 22, 2026 4:35am
cmux-staging Building Building Preview, Comment Jun 22, 2026 4:35am

@coderabbitai

coderabbitai Bot commented Jun 20, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Introduces hard deadlines for both QR/manual pairing and app-launch session restoration. A thread-safe RPCRequestTimeoutState<T> and parallel AuthPhaseTimeoutState<T> replace task-group-based timeouts. Protocol properties expose configurable pairing and session-restore deadlines. The pairing loop bounds each per-request RPC to remaining overall budget; session priming wraps token probes in tighter deadlines and treats cached users with tokens as immediately authenticated while validation occurs in background. Tests verify fast failure against slow operations.

Changes

Pairing-attempt deadline enforcement

Layer / File(s) Summary
Thread-safe RPC timeout continuation helper and session cleanup
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift, Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift
Adds RPCRequestTimeoutState<T> for lock-guarded exactly-once continuation resumption, and rewrites withRequestTimeout using withTaskCancellationHandler + withCheckedThrowingContinuation with separate operation and sleep tasks coordinated through the new state. Wraps connection setup in ensureConnected with cancellation-aware cleanup that closes the transport candidate on external cancellation.
pairingAttemptTimeoutNanoseconds protocol property and runtime conformance
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileSyncRuntime.swift, ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRuntime.swift
Adds pairingAttemptTimeoutNanoseconds: UInt64 to MobileSyncRuntime with default 8 seconds; wires a static default constant and both public initializer overloads in CMUXMobileRuntime to expose and store the configurable value.
Bounded per-request timeout inside the pairing loop
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
Records pairingAttemptStartedAt and sets activeRoute in connectManualHost before pairing; threads the attempt timestamp through manual-host ticket creation; computes remaining budget via boundedPairingRequestTimeoutNanoseconds(...) in the workspace-list loop; fails fast when budget is exhausted; substitutes bounded timeouts for fixed per-request timeouts; records DiagnosticEvent(.pairFail) on failure.
Deadline tests, scanner sheet dismiss, and test infrastructure
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobilePairingAttemptDeadlineTests.swift, MobileShellRenderGridLivenessTestSupport.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePairingScannerSheet.swift, Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCClientTests.swift, TransportTestDoubles.swift
Adds MobilePairingAttemptDeadlineTests with SlowIgnoringCancellationTransport and PairingDeadlineRuntime asserting sub-50 ms failure; adds dismiss() before onCode in the QR scanner sheet; passes macPairingCompatibilityVersion in makeTicket. Adds RPC client test verifying timeout closes slow connection before sending authenticated request; introduces SlowConnectTimeoutTransport test double with recorded payload tracking and polling helpers; adds closed() query to QueuedCancellationProbeTransport.

Session-restore deadline enforcement

Layer / File(s) Summary
Thread-safe auth timeout continuation helper
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthPhaseTimeout.swift
Adds AuthPhaseTimeoutState<T> for lock-guarded exactly-once continuation resumption, and rewrites withAuthPhaseTimeout using withTaskCancellationHandler + withCheckedThrowingContinuation with separate operation and deadline tasks coordinated through the new state; removes the prior AuthPhaseDeadlineExceeded marker error.
sessionRestore deadline in AuthTimeouts
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthTimeouts.swift
Adds a dedicated sessionRestore: Duration property with production default of 8 seconds, updates the initializer to optionally accept sessionRestore (defaulting to network when omitted), and sets the constant default to include the new deadline.
Cached auth state behavior—immediate authentication with validation in background
Packages/Shared/CMUXAuthCore/Sources/CMUXAuthCore/Models/CMUXAuthState.swift, Packages/Shared/CMUXAuthCore/Tests/CMUXAuthCoreTests/CMUXAuthStateTests.swift
Updates primed(...) to return isAuthenticated: true when both hasTokens and cachedUser are present, treating the cached user as immediately signed in while runtime validation occurs in background; refines autoLoginCredentials handling to only force restoring state when tokens are absent; updates test expectations to assert authentication, correct currentUser, and non-restoring state for cached-user-with-tokens, and nil currentUser for tokens-without-user.
Bounded token probes and user fetch in session priming
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator+SessionPriming.swift
Wraps accessToken/refreshToken probes inside runPhase(.validateSession, timeout: timeouts.sessionRestore) with error handling that preserves cached session on probe failure; updates currentUser fetch to use sessionRestore timeout; snapshots expectedRefreshToken via timed runPhase; replaces the failure path's direct refresh-token await with a timed check that treats failures as "preserve cached session".
Updated coordinator factory and timeout test for cached auth state
Packages/Shared/CmuxAuthRuntime/Tests/CmuxAuthRuntimeTests/AuthCoordinatorTimeoutTests.swift, TestPhaseSignal.swift
Extends makeCoordinator factory to initialize and wire session/identity caches with optional cachedUser and hasCachedTokens parameters; adds launchRestoreTokenProbeTimeoutKeepsCachedSessionInteractive test that configures cached state and uses a HangingLaunchTokenProbeAuthClient to simulate a hanging probe; introduces HangingLaunchTokenProbeAuthClient actor; adds didStart computed property to TestPhaseSignal.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant MobileShellComposite
  participant withRequestTimeout
  participant RPCRequestTimeoutState
  participant WorkspaceListRPC

  rect rgba(100, 149, 237, 0.5)
    note over MobileShellComposite: Pairing attempt starts
    User->>MobileShellComposite: connectManualHost / QR scan
    MobileShellComposite->>MobileShellComposite: record pairingAttemptStartedAt
  end

  loop Each candidate route
    MobileShellComposite->>MobileShellComposite: boundedPairingRequestTimeoutNanoseconds()
    alt budget exhausted
      MobileShellComposite-->>User: throw .requestTimedOut
    else budget remaining
      MobileShellComposite->>withRequestTimeout: call with bounded timeout
      withRequestTimeout->>RPCRequestTimeoutState: install continuation
      withRequestTimeout->>WorkspaceListRPC: start operation task
      withRequestTimeout->>withRequestTimeout: start sleep(boundedNanoseconds)
      alt RPC responds in time
        WorkspaceListRPC-->>RPCRequestTimeoutState: resume(returning:)
        RPCRequestTimeoutState-->>MobileShellComposite: workspace list
      else deadline fires
        withRequestTimeout-->>RPCRequestTimeoutState: resume(throwing: .requestTimedOut)
        RPCRequestTimeoutState-->>MobileShellComposite: .requestTimedOut
      end
    end
  end

  rect rgba(220, 80, 80, 0.5)
    note over MobileShellComposite: All routes failed
    MobileShellComposite->>MobileShellComposite: DiagnosticEvent(.pairFail)
    MobileShellComposite-->>User: throw lastError / .connectionClosed
  end
Loading
sequenceDiagram
  participant LaunchController
  participant AuthCoordinator
  participant withAuthPhaseTimeout
  participant AuthPhaseTimeoutState
  participant sessionProbeTask
  participant deadlineTask

  rect rgba(100, 149, 237, 0.5)
    note over AuthCoordinator: Session restore phase starts
    LaunchController->>AuthCoordinator: bootstrap()
    AuthCoordinator->>AuthCoordinator: validateSession with sessionRestore deadline
  end

  AuthCoordinator->>withAuthPhaseTimeout: runPhase(.validateSession, timeout: 8s)
  withAuthPhaseTimeout->>AuthPhaseTimeoutState: install continuation
  withAuthPhaseTimeout->>sessionProbeTask: start accessToken/refreshToken probes
  withAuthPhaseTimeout->>deadlineTask: start sleep(8 seconds)
  
  alt probes complete in time
    sessionProbeTask->>AuthPhaseTimeoutState: resume(returning: tokenState)
    AuthPhaseTimeoutState-->>AuthCoordinator: tokens validated
    AuthPhaseTimeoutState->>deadlineTask: cancel
  else 8-second deadline fires
    deadlineTask->>AuthPhaseTimeoutState: resume(throwing: .timedOut)
    AuthPhaseTimeoutState-->>AuthCoordinator: .timedOut
    AuthPhaseTimeoutState->>sessionProbeTask: cancel
    AuthCoordinator->>AuthCoordinator: preserve cached session
  end

  rect rgba(80, 220, 80, 0.5)
    note over AuthCoordinator: Session ready or preserved
    AuthCoordinator-->>LaunchController: bootstrap complete
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • manaflow-ai/cmux#5713: Modifies MobileShellComposite's pairing failure/timeout classification and surfacing, directly adjacent to this PR's per-request timeout and pairFail diagnostic event changes.
  • manaflow-ai/cmux#6028: Both PRs modify the iOS pairing flow in MobileShellComposite.swift—specifically connect(ticket:)—but the main PR changes timeout/deadline bounding logic while the retrieved PR changes pairing validation (email/version-warning gating).

Poem

🐇 Tick-tock, the pairing clock—eight seconds to knock!
Session restore counts down the same, racing against the deadline flame.
Cached users sign in at once, no delay for the launch blitz—
Both pause when ops run slow, both throw when time says no.
No wait forever more—I race, I cancel, I explore! ⏰


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (5 errors, 2 warnings)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error AuthPhaseTimeoutState in AuthPhaseTimeout.swift lacks documentation and lint:allow comment for its OSAllocatedUnfairLock, violating the rule of explicit isolation carve-outs. Add documentation comment explaining @unchecked Sendable carve-out and lint:allow comment above OSAllocatedUnfairLock, matching the pattern used in RPCRequestTimeoutState.
Cmux Swift Blocking Runtime ❌ Error Test file MobileCoreRPCClientTests.swift line 171 uses fixed Task.sleep(20ms) to wait for async readiness before assertion, violating guidelines; review feedback to replace with deterministic waitU... Replace line 171's fixed Task.sleep with await transport.waitUntilConnectAttemptFinished() or add a deterministic completion signal to SlowConnectTimeoutTransport.connect().
Cmux Swift Concurrency ❌ Error Test code contains fixed Task.sleep calls (lines 171 & 220 in MobileCoreRPCClientTests.swift) used for test synchronization instead of deterministic wait signals, violating test patterns per swift-... Replace fixed Task.sleep(nanoseconds: 20_000_000) calls with deterministic completion signals. Implement waitUntilConnectAttemptFinished() in SlowConnectTimeoutTransport to signal when connect() fully unwinds after close().
Cmux Architecture Rethink ❌ Error Test introduces fixed Task.sleep (20ms) to wait for async readiness before assertion in timedOutRPCClosesSlowConnectionBeforeSendingAuthenticatedRequest, violating the prior review comment requirin... Replace the 20ms Task.sleep with await transport.waitUntilConnectAttemptFinished() and implement that method in SlowConnectTimeoutTransport to resume a continuation from defer in connect().
Cmux No Test Or Debug Seam In Production Source ❌ Error PR adds public test-only seam debugWithRequestTimeout in #if DEBUG block in production source MobileCoreRPCClient.swift that exposes private withRequestTimeout for test access. Remove debugWithRequestTimeout from production source. Widen withRequestTimeout from private to internal and access via @testable import CmuxMobileRPC in test target, per PR #6452 pattern.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description provides a brief summary of changes but is missing critical required sections from the template (Testing details, Demo Video, Review Trigger, and Checklist). Add the missing sections from the template: expand Testing section with specific test commands and manual verification steps, include a Demo Video subsection (if applicable), add the Review Trigger block, and complete the Checklist with all items marked.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title concisely and accurately describes the main change: bounding iOS pairing attempts to an 8-second budget, which is the primary objective of this PR.
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 Expensive Synchronous Load ✅ Passed No expensive synchronous loaders (.load(), sysctl, FileManager) added to main actor or interactive paths; changes are timeout management, state initialization, and async continuation handling.
Cmux Cache Substitution Correctness ✅ Passed Cache substitution is documented and properly handled: CMUXAuthState.primed() treats cached user+tokens as authenticated immediately while event-driven async validation via validateCachedSession()...
Cmux No Hacky Sleeps ✅ Passed This PR's scope is limited to Swift files (MobileCoreRPCClient, AuthPhaseTimeout, CMUXAuthState, etc.). The "cmux no hacky sleeps" check applies to TypeScript, JavaScript, shell, and non-Swift buil...
Cmux Algorithmic Complexity ✅ Passed Production code in this PR contains only fixed-size collection loops (2-4 routes, 1-3 request variants) with O(1) timeout calculations—no nested full-collection scans, per-target rescans, or unboun...
Cmux Swift @Concurrent ✅ Passed Async timeout coordination helpers (withAuthPhaseTimeout, withRequestTimeout) don't perform CPU/file/network-heavy work themselves—they only coordinate task races and delegates actual work to passe...
Cmux Swift File And Package Boundaries ✅ Passed No violations of Swift file/package boundaries: all changes respect 250-line threshold for oversized files, new helpers are private with clear single responsibility, and logic is properly packaged...
Cmux Swiftpm Lockfiles ✅ Passed All cmux-owned package .gitignore files correctly do not ignore Package.resolved; root Xcode Package.resolved is included; this PR contains code changes (timeout handling), not dependency resolutio...
Cmux Swift Logging ✅ Passed PR contains no logging violations: no print/NSLog/debugPrint in production code; Logger declarations are file-scoped private; all error logs with sensitive data use privacy: .private markers; uses...
Cmux User-Facing Error Privacy ✅ Passed PR introduces new error types (requestTimedOut, timedOut, connectionClosed) with safe generic descriptions; all logging uses privacy markers for sensitive details; no upstream vendor names, credent...
Cmux Full Internationalization ✅ Passed PR adds only internal timeout configuration properties and refactors cancellation logic; no new user-facing text, no string resource changes, existing UI properly uses L10n.string() localization.
Cmux Swiftui State Layout ✅ Passed No SwiftUI state layout violations detected. PR contains existing @Observable in MobileShellComposite (legacy, untouched) and modifies only business logic (timeouts, pairing), not state patterns. M...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup declarations or changes. MobilePairingScannerSheet is a SwiftUI View (sheet modal), an allowed case per the rule.
Cmux Source Artifacts ✅ Passed All 17 changed files are hand-written Swift source/test code in standard package directories (Sources/ and Tests/); no artifacts like DerivedData, build output, caches, or generated files detected.
✨ 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-pairing-timeout

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 and usage tips.

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift (1)

3328-3350: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Start the deadline at the user pairing attempt, not inside connect(ticket:).

Line 3328 resets the budget after pre-connect work, but manual pairing starts earlier and can await manualHostTicket(...) / mobile.attach_ticket.create before reaching this loop. A slow manual attach-ticket RPC can therefore exceed the intended 8-second attempt window and still get a fresh per-route budget here. Carry the beginPairingAttempt start instant through manual ticket creation and this route loop, and bound that pre-connect RPC too.

🤖 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/Sources/CmuxMobileShell/MobileShellComposite.swift`
around lines 3328 - 3350, The deadline for the pairing attempt should start
before manual ticket creation, not at line 3328 inside the route loop. Move the
initialization of pairingAttemptStartedAt to an earlier point in the pairing
flow (before manualHostTicket or mobile.attach_ticket.create RPC calls), then
pass this earlier timestamp through to the route loop where supportedRoutes is
iterated and boundedPairingRequestTimeoutNanoseconds is called. This ensures the
entire manual pairing attempt, including the attach_ticket RPC and all route
attempts, is bounded by the 8-second window starting from when pairing actually
begins, not reset at this intermediate point.
🤖 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/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 3397-3413: In the boundedPairingRequestTimeoutNanoseconds
function, replace the direct call to Date() with runtime.now() when calculating
elapsedSeconds. This ensures consistency with other usages throughout the file
(lines 115, 122, 193, 3622, 3649) and maintains proper dependency injection for
testability. Update the line that calculates elapsedSeconds to use
runtime.now().timeIntervalSince(attemptStartedAt) instead of
Date().timeIntervalSince(attemptStartedAt).

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobilePairingAttemptDeadlineTests.swift`:
- Around line 11-22: The test
qrPairingURLTimesOutWithoutWaitingForStuckTransport uses real wall-clock time
assertions (Date().timeIntervalSince(startedAt) < 0.05 on line 21) which makes
the test flaky under CI load. Replace the wall-clock timing expectation with a
virtual or fake clock mechanism that advances deterministically. Update the fake
transport implementation (referenced around lines 79-82) to use the same virtual
clock instead of real-time delays. Assert on causality transitions (such as
state changes or timeout behavior) rather than elapsed duration thresholds to
ensure the test is deterministic and not dependent on actual machine
performance.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 3328-3350: The deadline for the pairing attempt should start
before manual ticket creation, not at line 3328 inside the route loop. Move the
initialization of pairingAttemptStartedAt to an earlier point in the pairing
flow (before manualHostTicket or mobile.attach_ticket.create RPC calls), then
pass this earlier timestamp through to the route loop where supportedRoutes is
iterated and boundedPairingRequestTimeoutNanoseconds is called. This ensures the
entire manual pairing attempt, including the attach_ticket RPC and all route
attempts, is bounded by the 8-second window starting from when pairing actually
begins, not reset at this intermediate point.
🪄 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: bc575ef3-a47d-438b-b6d7-5a4626e3872c

📥 Commits

Reviewing files that changed from the base of the PR and between bbed84b and 5686dec.

📒 Files selected for processing (7)
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileSyncRuntime.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobilePairingAttemptDeadlineTests.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePairingScannerSheet.swift
  • ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRuntime.swift

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated
@greptile-apps

greptile-apps Bot commented Jun 20, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces a single 8-second end-to-end budget for iOS pairing attempts (QR, pasted QR, manual host), replaces nested per-subphase timeouts with a shared RPCRequestDeadline, and tightens auth lifecycle tracking so sign-out can cancel in-flight credential exchanges, validations, and post-sign-in hooks without leaving stale token writes behind.

  • RPC layer: New RPCRequestDeadline, RPCStackTokenGate, and MobileRPCConnectAttemptRegistry actors cap token acquisition, deduplicate concurrent token fetches per shell, and limit stacked connect attempts per route; timed-out connections are torn down in the background so other waiters are not blocked.
  • Auth layer: AuthCoordinator gains coordinator-owned task tracking for every token-touching phase; beginSignInFlow now awaits a preflight quiesce before proceeding; sessionRestore timeout preserves the cached session instead of leaving the app on "Restoring session"; .sendCode is now cleared on sign-out.
  • CLI: New cmux sessions list command reads ~/.cmuxterm/*-hook-sessions.json offline with filters and Codex transcript/index hints; all strings are localized in en and ja.

Confidence Score: 3/5

Two issues in the RPC layer need attention before merge: a named test seam in production Sources, and a retry gate that incorrectly fires on user cancellation as well as genuine timeouts.

The cancelWaiter path in RPCStackTokenGate is byte-for-byte identical to timeoutWaiter, setting a 30-second retry gate when all pairing callers cancel. A user who dismisses and immediately re-opens the pairing sheet will see an instant requestTimedOut failure for the next 30 seconds. The connectWaiterCountForTesting() method in MobileCoreRPCSession is a named test seam in production Sources, which breaks the established rule to expose state via @testable import on widened internal members instead.

RPCStackTokenGate.swift (cancelWaiter logic) and MobileCoreRPCSession.swift (connectWaiterCountForTesting accessor).

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/RPCStackTokenGate.swift New actor that deduplicates Stack token acquisition across RPC clients; cancelWaiter is identical to timeoutWaiter causing a 30 s retry gate after user-initiated cancellations.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift Significantly extended with per-route connect gating, waiter tracking, and deadline-aware send; introduces connectWaiterCountForTesting() test seam in production Sources.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift Replaced per-subphase timeouts with a single end-to-end RPCRequestDeadline; removed #if DEBUG debugWithRequestTimeout test seam; looks correct.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ManualAttachTicket.swift New extension file housing manual-attach-ticket helpers; elapsed time measured via runtime.now() (injected clock), correctly bounded.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileRPCConnectAttemptRegistry.swift New actor tracking per-route connect attempts with hard-gate after repeated abandoned cleanups; DispatchTime-based expiry consistent with the rest of the RPC layer.
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator.swift Sign-out now cancels/waits on validations, post-sign-in hooks, and token-touching phases; beginSignInFlow awaits preflight quiescence; .sendCode added to phaseTimeoutRegistry.clear(). Looks correct.
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthPhaseTimeout.swift Replaced structured task-group with AsyncThrowingStream race; loser task intentionally not joined to avoid blocking on SDK calls that ignore cancellation.
Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthPhaseTimeoutRegistry.swift New actor gating retry attempts after phase timeouts; markTimedOut guard against stale IDs prevents premature locking.
CLI/CMUXCLI+SessionsList.swift New cmux sessions list command reading hook-session JSON files; runs synchronously in a CLI context (not on MainActor); localized strings present for all supported locales.
Resources/Localizable.xcstrings All new cli.sessions.* keys include both en and ja translations, matching the catalog's existing locale set.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePairingScannerSheet.swift One-line change: sheet dismisses before handing scanned QR to the pairing handler, preventing UI stacking.

Reviews (20): Last reviewed commit: "Deflake auth runtime package CI" | Re-trigger Greptile

Comment on lines +3397 to +3413
private static func boundedPairingRequestTimeoutNanoseconds(
runtime: any MobileSyncRuntime,
attemptStartedAt: Date
) -> UInt64 {
let requestTimeout = runtime.pairingRequestTimeoutNanoseconds
let attemptTimeout = runtime.pairingAttemptTimeoutNanoseconds
guard attemptTimeout > 0 else {
return requestTimeout
}

let elapsedSeconds = max(0, Date().timeIntervalSince(attemptStartedAt))
let elapsedNanoseconds = UInt64((elapsedSeconds * 1_000_000_000).rounded(.up))
guard elapsedNanoseconds < attemptTimeout else {
return 0
}
return min(requestTimeout, attemptTimeout - elapsedNanoseconds)
}

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 Wall-clock Date() bypasses the injected runtime.now clock

boundedPairingRequestTimeoutNanoseconds calls Date() directly to measure elapsed time, and the call site captures pairingAttemptStartedAt the same way. MobileSyncRuntime already carries a now: @Sendable () -> Date injectable clock precisely so time-dependent paths can be exercised deterministically.

Because both reads use the system clock, the new deadline math cannot be controlled in tests — the test in MobilePairingAttemptDeadlineTests works around this by setting pairingAttemptTimeoutNanoseconds to a 1 ms budget instead of driving injected time. Passing runtime.now into boundedPairingRequestTimeoutNanoseconds (and using it for pairingAttemptStartedAt) would keep the approach consistent with the rest of the codebase.

@blacksmith-sh

This comment has been minimized.

Comment on lines +6 to +47
private final class RPCRequestTimeoutState<T: Sendable>: @unchecked Sendable {
private let lock = NSLock()
private var continuation: CheckedContinuation<T, any Error>?
private var cancelHandler: (@Sendable () -> Void)?

func install(_ continuation: CheckedContinuation<T, any Error>) {
lock.lock()
self.continuation = continuation
lock.unlock()
}

func setCancelHandler(_ handler: @escaping @Sendable () -> Void) {
lock.lock()
cancelHandler = handler
lock.unlock()
}

func resume(returning value: T) {
finish { $0.resume(returning: value) }
}

func resume(throwing error: any Error) {
finish { $0.resume(throwing: error) }
}

func cancel() {
finish { $0.resume(throwing: CancellationError()) }
}

private func finish(_ resume: (CheckedContinuation<T, any Error>) -> Void) {
lock.lock()
let continuation = continuation
let cancelHandler = cancelHandler
self.continuation = nil
self.cancelHandler = nil
lock.unlock()

guard let continuation else { return }
cancelHandler?()
resume(continuation)
}
}

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.

P1 RPCRequestTimeoutState is missing the finished flag, risking a leaked CheckedContinuation

AuthPhaseTimeoutState (the direct model for this class) guards both install and setCancelHandler against a prior cancel() call via a finished: Bool flag. RPCRequestTimeoutState does not: its install unconditionally stores the continuation, and its setCancelHandler never calls the handler eagerly.

Concrete failure: withTaskCancellationHandler guarantees that if the task is already cancelled when the function is called, onCancel fires before the body runs. If that happens, state.cancel() reads a nil continuation and returns without resuming anything; then install stores the continuation that nothing will ever resume. The result is a permanently hung CheckedContinuation — a logged runtime warning in release and a crash on dealloc in debug builds — leaving the pairing spinner active indefinitely.

Second gap: if cancel() fires after install but before setCancelHandler, the continuation is correctly resumed with CancellationError, but operationTask and timeoutTask are never cancelled because setCancelHandler does not call the handler eagerly when already finished. The AuthPhaseTimeoutState addresses this with if finished { handler() } in setCancelHandler. The same two-line pattern needs to be added here.

@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: 4

🤖 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/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator`+SessionPriming.swift:
- Around line 90-94: The session restoration process is giving each runPhase
call a fresh full timeout instead of sharing a single budget across all phases.
Capture the restore start time once at the beginning of the checkExistingSession
method, then modify all runPhase calls (including those in the validateSession
phase around lines 90-94 and the validateCachedSession phase around lines
185-201 and 232-234) to pass the remaining time calculated from the initial
start time instead of using the fixed timeouts.sessionRestore timeout. This
ensures that slow token probes or validation checks consume from a single shared
budget rather than each phase getting its own full allocation.
- Around line 229-239: The catch block for the session refresh-token validation
does not verify that sessionGeneration remains unchanged before calling
preserveCachedSessionAfterValidationFailure(), which can resurrect stale cached
auth state if the generation is bumped during the await on line 232. Add a
sessionGeneration guard check in the catch block before invoking
preserveCachedSessionAfterValidationFailure(), mirroring the validation guard
used in the successful code path at line 240 to ensure the session has not been
invalidated by a sign-out or newer sign-in during the restore timeout period.

In
`@Packages/Shared/CmuxAuthRuntime/Tests/CmuxAuthRuntimeTests/AuthCoordinatorTimeoutTests.swift`:
- Around line 90-99: The test uses a fixed Task.sleep with milliseconds before
asserting on the completion signal, which relies on timing and can fail under
load. Instead of the Task.sleep call on line 97, await the completion signal
that was already created. Replace the sleep with code that waits for the
completion signal to be marked as started (by awaiting a method on the
completion object or similar mechanism) before the assertion on didStart,
ensuring the test waits on a real async signal rather than wall-clock time.
- Around line 169-171: The while true loop with try? await Task.sleep(for:
.seconds(3600)) creates a post-cancellation busy loop because Task.sleep throws
on cancellation, the try? silently catches it, and the loop continues spinning
forever. Replace this cancellation-aware suspension with a
non-cancellation-aware suspension mechanism that will keep the task genuinely
suspended even after cancellation occurs, such as using DispatchSemaphore with a
blocking wait or Thread.sleep, which do not respond to task cancellation.
🪄 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: e05aa21a-105e-48a6-a2b2-728d36196dce

📥 Commits

Reviewing files that changed from the base of the PR and between 5686dec and d5902c4.

📒 Files selected for processing (5)
  • Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator+SessionPriming.swift
  • Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthPhaseTimeout.swift
  • Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthTimeouts.swift
  • Packages/Shared/CmuxAuthRuntime/Tests/CmuxAuthRuntimeTests/AuthCoordinatorTimeoutTests.swift
  • Packages/Shared/CmuxAuthRuntime/Tests/CmuxAuthRuntimeTests/TestPhaseSignal.swift

Comment on lines +90 to +94
hasStoredTokens = try await runPhase(.validateSession, timeout: timeouts.sessionRestore) {
let hasAccessToken = await client.accessToken() != nil
let hasRefreshToken = await client.refreshToken() != nil
return hasAccessToken || hasRefreshToken
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Carry one remaining restore budget instead of restarting sessionRestore for each phase.

Each runPhase(... timeout: timeouts.sessionRestore) gets a fresh deadline, so a slow token probe can consume nearly the whole launch budget and validateCachedSession can then spend another full budget on currentUser/refresh-token checks. That weakens the PR’s hard launch-restore deadline and can keep “Restoring session” visible longer than configured. Capture the restore start once in checkExistingSession and pass remaining time into the nested probes/validation. Based on PR context: “Adds a hard deadline for session restoration at app launch.”

Also applies to: 185-201, 232-234

🤖 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/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthCoordinator`+SessionPriming.swift
around lines 90 - 94, The session restoration process is giving each runPhase
call a fresh full timeout instead of sharing a single budget across all phases.
Capture the restore start time once at the beginning of the checkExistingSession
method, then modify all runPhase calls (including those in the validateSession
phase around lines 90-94 and the validateCachedSession phase around lines
185-201 and 232-234) to pass the remaining time calculated from the initial
start time instead of using the fixed timeouts.sessionRestore timeout. This
ensures that slow token probes or validation checks consume from a single shared
budget rather than each phase getting its own full allocation.

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift (1)

1196-1206: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Re-check attempt currency before minting the manual attach ticket.

failPairingIfOffline awaits and can return .proceed without checking attemptID when the device is online. If the user cancels or starts another pairing during that await, this path still calls manualHostTicket and reads the now-global pairingAttemptStartedAt, so a superseded manual attempt can issue mobile.attach_ticket.create under the wrong budget.

Proposed fix
         activeRoute = directRoute
         let attemptID = beginPairingAttempt(method: "manual")
+        let attemptStartedAt = pairingAttemptStartedAt
         // Fast offline preflight: fail immediately instead of stacking
         // per-route timeouts into the opaque ~60s blob.
         let manualRoutes = directRoute.map { [$0] } ?? []
         guard await failPairingIfOffline(attemptID: attemptID, phase: "preflight", routes: manualRoutes) == .proceed else { return }
+        guard isCurrentPairingAttempt(attemptID) else { return }
         do {
             let ticket = try await manualHostTicket(
                 name: trimmedName,
                 host: normalizedHost,
                 port: port,
-                attemptStartedAt: pairingAttemptStartedAt
+                attemptStartedAt: attemptStartedAt
             )
🤖 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/Sources/CmuxMobileShell/MobileShellComposite.swift`
around lines 1196 - 1206, After the guard statement where failPairingIfOffline
returns proceed, add an additional check to verify that the attemptID is still
current before proceeding to call manualHostTicket. The issue is that during the
await on failPairingIfOffline, the user could cancel or start another pairing
attempt, which would make the current attemptID stale. Without re-checking the
attempt currency, the code will call manualHostTicket with an outdated attemptID
and use the now-global pairingAttemptStartedAt value, causing a superseded
manual attempt to potentially issue the attach ticket under the wrong budget.
Add a validation after the guard statement to ensure attemptID hasn't been
superseded before proceeding with the ticket creation.
🤖 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.

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 1196-1206: After the guard statement where failPairingIfOffline
returns proceed, add an additional check to verify that the attemptID is still
current before proceeding to call manualHostTicket. The issue is that during the
await on failPairingIfOffline, the user could cancel or start another pairing
attempt, which would make the current attemptID stale. Without re-checking the
attempt currency, the code will call manualHostTicket with an outdated attemptID
and use the now-global pairingAttemptStartedAt value, causing a superseded
manual attempt to potentially issue the attach ticket under the wrong budget.
Add a validation after the guard statement to ensure attemptID hasn't been
superseded before proceeding with the ticket creation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c4729552-9d78-4628-992c-84f599f3fc9c

📥 Commits

Reviewing files that changed from the base of the PR and between 0b0b726 and df6ad97.

📒 Files selected for processing (5)
  • Packages/Shared/CMUXAuthCore/Sources/CMUXAuthCore/Models/CMUXAuthState.swift
  • Packages/Shared/CMUXAuthCore/Tests/CMUXAuthCoreTests/CMUXAuthStateTests.swift
  • Packages/Shared/CmuxAuthRuntime/Sources/CmuxAuthRuntime/Coordinator/AuthPhaseTimeout.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift (1)

225-231: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Start the request deadline before auth augmentation.

Line 225 awaits requestDataWithAuth before the timeout race is installed, but that path can await runtime.stackAccessTokenProvider() at Lines 290 and 322. A hung token provider can still block QR/manual pairing beyond the remaining budget before withRequestTimeout starts, so the “hard” request deadline is not actually end-to-end.

🐛 Proposed fix
-        let authenticated = try await requestDataWithAuth(augmented)
         return try await Self.withRequestTimeout(
             timeoutNanoseconds: timeoutNanoseconds ?? runtime.rpcRequestTimeoutNanoseconds
         ) {
+            let authenticated = try await self.requestDataWithAuth(augmented)
+            try Task.checkCancellation()
             try await self.session.send(payload: authenticated, requestID: id)
         } cancelOperation: {
             await self.session.tearDown(error: .connectionClosed)
         }
🤖 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/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift`
around lines 225 - 231, The request timeout deadline is being installed after
the requestDataWithAuth call completes, but this authentication call can
potentially await runtime.stackAccessTokenProvider() which may hang, causing the
operation to exceed the intended timeout. Move the withRequestTimeout wrapper to
encompass the entire operation including the requestDataWithAuth call at line
225, so that both the authentication augmentation and the session.send operation
are protected by the timeout deadline from the start.
🤖 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/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCClientTests.swift`:
- Around line 170-172: Replace the fixed 20ms sleep in the test (the Task.sleep
call) with a deterministic wait mechanism. In the SlowConnectTimeoutTransport
test double class, add a new method called waitUntilConnectAttemptFinished()
that returns when the connect() method has fully unwound after close() resumes
it. Implement this method by using a continuation that resumes from within a
defer block in the connect() method to signal completion. Then, in the test at
line 170-172, replace the Task.sleep call with an await to this new
waitUntilConnectAttemptFinished() method before asserting on
sentRequests().isEmpty.

---

Outside diff comments:
In `@Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift`:
- Around line 225-231: The request timeout deadline is being installed after the
requestDataWithAuth call completes, but this authentication call can potentially
await runtime.stackAccessTokenProvider() which may hang, causing the operation
to exceed the intended timeout. Move the withRequestTimeout wrapper to encompass
the entire operation including the requestDataWithAuth call at line 225, so that
both the authentication augmentation and the session.send operation are
protected by the timeout deadline from the start.
🪄 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: 1347297b-d072-4ddb-9c66-3598b87ddd5d

📥 Commits

Reviewing files that changed from the base of the PR and between df6ad97 and c2ec2a1.

📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCClientTests.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/TransportTestDoubles.swift

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated
@lawrencecchen
lawrencecchen force-pushed the feat-ios-pairing-timeout branch from 746ce57 to a40c7d1 Compare June 20, 2026 11:26
# Conflicts:
#	Packages/Shared/CmuxAuthRuntime/Tests/CmuxAuthRuntimeTests/HostBrowserSignInFlowTests.swift
#	Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift
#	Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift

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

Cursor Bugbot has reviewed your changes 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 ad0fa68. Configure here.

@lawrencecchen
lawrencecchen merged commit cd09255 into main Jun 22, 2026
37 checks passed
@lawrencecchen
lawrencecchen deleted the feat-ios-pairing-timeout branch June 22, 2026 04:58
@lawrencecchen
lawrencecchen restored the feat-ios-pairing-timeout branch July 18, 2026 10:18

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant