Skip to content

fix(ios): anchor Iroh connection diagnostics - #8456

Merged
5 commits merged into
mainfrom
feat-iroh-diag-owner
Jul 19, 2026
Merged

5 commits merged into
mainfrom
feat-iroh-diag-owner

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 19, 2026 •

Copy link
Copy Markdown
Collaborator

Summary\n- share one production diagnostics ring between the Iroh runtime and mobile shell\n- label foreground, background, probe, and feature-lane Iroh sessions locally\n- report the active foreground control path instead of whichever pooled session changed last\n- keep exported reports bounded and redacted\n\n## Verification\n- CmxIrohTransport: 380 tests passed\n- CmuxMobileShell: 597 tests passed\n- CmuxMobileRPC: 101 tests passed\n- CMUXMobileCore full suite passed\n- regression test failed in the first commit and passed after the fix\n- tagged macOS build irdg connected an isolated iOS Simulator without QR\n- authenticated Iroh workspace and terminal rendering survived app terminate/relaunch\n- git diff --check\n\n## Risk\nSession purpose is process-local and never enters the protocol. Path reporting now prefers the visible foreground control session, with control and pooled fallbacks when no foreground owner exists.


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


Summary by cubic

Fixes iOS Iroh diagnostics to always report the foreground control path and uses a single production diagnostic log owned by the iOS root scene, shared by the runtime and shell. Path-change events only fire for established sessions; background and feature lanes no longer override the visible path.

  • Bug Fixes

    • Added CmxTransportSessionPurpose and threaded it through CmxByteTransportRequest and MobileCoreRPCClient to label sessions as foreground, background, probe, or feature-lane.
    • Updated CmxIrohClientSessionPool to prefer the foreground control session for selectedObservedPath(); background/feature sessions cannot replace it, and selected-path changes publish only after a session is established.
    • Marked mobile shell calls with the correct purpose (.probe, .featureLane, .backgroundControl) to anchor diagnostics to the foreground.
  • Migration

    • On iOS, pass a DiagnosticLog to the CMUXMobileRootScene initializer (required).

Written for commit 2be2f03. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added transport session purposes: foreground, background, probe, and feature lane, and propagated them through client and request setup.
    • Selected-path behavior is now purpose-aware for control ownership.
    • Manual attach and terminal-lane flows now use dedicated purposes.
  • Bug Fixes

    • Foreground control sessions are now preferred over background/feature sessions when selecting the active communication path.
    • Selected-path publication is deferred until an established control session exists.
  • Diagnostics

    • Diagnostic logging is now consistently enabled across builds.
  • Tests

    • Added coverage for purpose-aware selected-path publication and selection.

@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai

coderabbitai Bot commented Jul 19, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Transport requests now carry a session purpose, callers classify control, probe, and feature-lane connections, and the session pool preserves purpose during ownership transitions to prioritize foreground control paths. The iOS root scene now requires and consistently passes a diagnostic log.

Changes

Transport session purpose

Layer / File(s) Summary
Session purpose contract
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxTransportSessionPurpose.swift, Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxByteTransportRequest.swift, Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventCode.swift
Adds the public session-purpose enum, stores it on byte transport requests with a foreground-control default, and documents foreground precedence for selected paths.
Purpose propagation to transport requests
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/...
Passes .probe, .backgroundControl, and .featureLane from the relevant shell flows into transport requests.
Purpose-aware control ownership and path selection
Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientSessionPool.swift, Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/*
Stores purpose with control owners, preserves it through waiting and hand-off, prioritizes foreground control sessions, and tests selection and connection timing.

Diagnostic log wiring

Layer / File(s) Summary
Required diagnostic log in root scene
ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift
Makes diagnosticLog required and collapses DEBUG-specific store construction into one path that always passes the log.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MobileCoreRPCClient
  participant CmxByteTransportRequest
  participant CmxIrohClientSessionPool
  participant SelectedPathObserver
  MobileCoreRPCClient->>CmxByteTransportRequest: create request with sessionPurpose
  CmxByteTransportRequest->>CmxIrohClientSessionPool: acquire control session
  CmxIrohClientSessionPool->>CmxIrohClientSessionPool: store ControlOwner id and purpose
  CmxIrohClientSessionPool->>SelectedPathObserver: publish selected path
Loading

Possibly related PRs

  • manaflow-ai/cmux#7238: Both changes modify MobileCoreRPCClient and its CmxByteTransportRequest construction path.
  • manaflow-ai/cmux#8286: Both changes modify session invalidation, control-owner release, and selected-path handling.
  • manaflow-ai/cmux#8398: Both changes touch the selectedPathChanged diagnostic event.

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 25
✅ Passed checks (25 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 The new value types are nonisolated top-level models, the RPC client stays immutable Sendable, and store construction remains on @MainActor; the session pool is already an actor.
Cmux Swift Blocking Runtime ✅ Passed No new blocking/sleep/sync/lock primitives were introduced in production Swift; the only polling/yield loops are test-only scaffolding, and runtime code uses actors/continuations.
Cmux Browser Automation Off-Main ✅ Passed PR only changes Iroh diagnostics/session-purpose plumbing; it doesn't touch browser automation routing or the rule's target files, so the check is not applicable.
Cmux Expensive Synchronous Load ✅ Passed Touched Swift changes only add session-purpose wiring and path selection; no new synchronous history/JSON loads, Task.detached omissions, or main-actor agent-history access appear in the diff.
Cmux Cache Substitution Correctness ✅ Passed The path-selection change is event-driven and freshness-checked (only publishes after an established session); no persistence/history/undo cache substitution appears in the diff.
Cmux No Hacky Sleeps ✅ Passed Diff changes only Swift files; no TypeScript/JS/shell/build-runtime sleeps or waits were introduced.
Cmux Algorithmic Complexity ✅ Passed Changed prod code is just session-purpose plumbing; the only scalable scan is selectedObservedPath’s linear sessionOrder lookup, with no nested rescans or hot-path sort/filter loops.
Cmux Swift Concurrency ✅ Passed PASS: The full PR diff adds no new DispatchQueue/Combine/completion-handler legacy async patterns; added Task usage is test-only, and production async code is existing or unchanged.
Cmux Swift @Concurrent ✅ Passed No new nonisolated async work or @concurrent misuse was introduced; touched async helpers remain actor-isolated or existing UI-bound code.
Cmux Swift Package Boundaries ✅ Passed The only app-root change is CMUXMobileRootScene wiring diagnosticLog into CMUXMobileShellStore; the new session/path logic lives in package targets, not the app target.
Cmux Swiftpm Lockfiles ✅ Passed PR diff only changes source/test files; no Package.swift, Package.resolved, .gitignore, workflow, or Xcode project refs changed, so the lockfile rule isn’t triggered.
Cmux Swift Logging ✅ Passed Patch only changes selected-path publication logic; no print/debugPrint/dump/NSLog or ad hoc logging was added or changed.
Cmux User-Facing Error Privacy ✅ Passed Diff only adds sessionPurpose plumbing, one enum, test coverage, and a doc comment tweak; no user-facing error/alert/copy text was introduced or exposed.
Cmux Full Internationalization ✅ Passed Touched files are protocol/diagnostic logic and comments only; no new user-facing strings, localization APIs, .xcstrings/.plist locale edits, or web i18n files.
Cmux Swiftui State Layout ✅ Passed The touched SwiftUI root scene only threads a required diagnosticLog and its body has no new ObservableObject, GeometryReader, lazy-row stores, or render-time state writes.
Cmux Architecture Rethink ✅ Passed The only actual diff is a pool-side invariant fix: publish selected-path changes only after a session exists; no new timing, locks, observers, or split ownership were added.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No standalone cmux-owned windows or close-shortcut routing changed in the PR diff; only transport, diagnostics, and root-scene plumbing were touched.
Cmux Source Artifacts ✅ Passed Changed paths are hand-written source/tests only; none match artifact/tmp/build/log/screenshot patterns in the policy.
Cmux No Test Or Debug Seam In Production Source ✅ Passed PASS: no added debug/test-only symbols in production sources; the only #if DEBUG in the diff is removed from CMUXMobileRootScene, unifying a real store init path.
Cmux No Ambient Global State ✅ Passed No new ambient global state: the diff adds only scoped actor/type state, a new enum, and file/private helpers; no new top-level mutable vars or singletons.
Title check ✅ Passed The title is concise and clearly matches the main change: anchoring Iroh connection diagnostics on iOS.
Description check ✅ Passed The description includes a solid summary and verification section that match the template, with only some optional sections missing.
✨ 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-iroh-diag-owner

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 Jul 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes iOS Iroh diagnostics by introducing CmxTransportSessionPurpose to label sessions as foreground, background, probe, or feature-lane, and threads this label through CmxByteTransportRequest and MobileCoreRPCClient. selectedObservedPath() now prefers the established foreground control session over background/feature sessions, and path-change notifications are deferred until a session is actually established.

  • CmxIrohClientSessionPool: controlOwners upgraded from [SessionKey: UUID] to [SessionKey: ControlOwner] carrying both id and purpose; selectedObservedPath() now searches foreground → control → any-established in priority order; new publishSelectedPathChangeIfEstablished helper gates notifications on session presence.
  • CMUXMobileRootScene: diagnosticLog is now DiagnosticLog (non-optional) on iOS and always passed to CMUXMobileShellStore, removing the old #if DEBUG split and unifying the production diagnostics ring.
  • TestHangingDialEndpoint: Race-condition fix moves startedContinuation.yield() inside withCheckedThrowingContinuation, after pendingConnect is stored, so test continuations are only signaled once the test can safely resume them.

Confidence Score: 5/5

Safe to merge — the changes are well-scoped, thoroughly tested with two new regression tests, and the CmxIrohClientSessionPool actor isolation is maintained throughout.

The foreground-preference logic in selectedObservedPath() and the publishSelectedPathChangeIfEstablished guard have been traced through all call sites (sessionDidClose, invalidateSession, releaseControlSession, reserveControlOwner) and produce no double-publish or stale-notification scenarios. The TestHangingDialEndpoint race fix is correct. No blocking primitives, no test seams in production source, and no ambient globals were introduced.

No files require special attention — the session pool changes are fully covered by the new tests and the classification labels are consistently applied across all shell call sites.

Important Files Changed

Filename Overview
Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientSessionPool.swift Core fix: ControlOwner struct adds purpose awareness, selectedObservedPath() uses a foreground→control→fallback priority chain, and publishSelectedPathChangeIfEstablished correctly defers path-change notifications until a session is established.
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxTransportSessionPurpose.swift New enum with four well-documented cases; process-local only with no wire representation. Clean design.
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/CmxByteTransportRequest.swift Adds sessionPurpose field with .foregroundControl default, preserving backward compatibility for all existing call sites.
ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift Makes diagnosticLog non-optional on iOS, removes the #if DEBUG split for CMUXMobileShellStore, and updates the property doc comment. Correctly uses #if os(iOS) conditional compilation to keep DiagnosticLog? on non-iOS paths.
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientSessionPoolTests.swift Two new targeted tests verify (1) path changes are not published before a control session is established, and (2) foreground sessions win over newer background sessions.
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/TestHangingDialEndpoint.swift Race-condition fix: startedContinuation.yield() moved inside withCheckedThrowingContinuation so tests only see the start event after pendingConnect is stored and can be safely resumed.
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCClient.swift Adds sessionPurpose parameter (defaulting to .foregroundControl) and threads it through CmxByteTransportRequest. Clean API extension with backward-compatible default.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ManualAttachTicket.swift Labels the manual-attach probe client as .probe, correctly preventing it from competing with the foreground diagnostics path.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalLane.swift Labels the terminal lane request as .featureLane. Correct classification — terminal lanes are independent feature lanes sharing the admitted session.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Labels secondary-host status-fetch clients as .backgroundControl, correctly preventing non-selected Mac sessions from overriding the foreground path.
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventCode.swift Doc comment for selectedPathChanged updated to document the foreground-wins priority rule. No behavioral change.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[selectedObservedPath called] --> B{Find foregroundKey\ncontrolOwners.purpose == .foregroundControl\nAND sessions != nil}
    B -- found --> E[Return foreground session path]
    B -- not found --> C{Find controlKey\ncontrolOwners != nil\nAND sessions != nil}
    C -- found --> F[Return control session path]
    C -- not found --> D{sessionOrder.last\nAND sessions != nil}
    D -- found --> G[Return any established session path]
    D -- not found --> H[Return .unavailable]
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[selectedObservedPath called] --> B{Find foregroundKey\ncontrolOwners.purpose == .foregroundControl\nAND sessions != nil}
    B -- found --> E[Return foreground session path]
    B -- not found --> C{Find controlKey\ncontrolOwners != nil\nAND sessions != nil}
    C -- found --> F[Return control session path]
    C -- not found --> D{sessionOrder.last\nAND sessions != nil}
    D -- found --> G[Return any established session path]
    D -- not found --> H[Return .unavailable]
Loading

Reviews (3): Last reviewed commit: "docs(ios): state production diagnostic o..." | Re-trigger Greptile

Comment on lines 349 to 384
@@ -348,17 +367,19 @@ actor CmxIrohClientSessionPool {
return
}
if let existing = controlOwners[key] {
if existing == ownerID {
if existing.id == ownerID {
continuation.resume()
} else {
controlWaiters[key, default: []].append(ControlWaiter(
id: waiterID,
ownerID: ownerID,
purpose: purpose,
continuation: continuation
))
}
} else {
controlOwners[key] = ownerID
controlOwners[key] = ControlOwner(id: ownerID, purpose: purpose)
publishSelectedPathChange()
continuation.resume()
}

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 publishSelectedPathChange() fires before the session is established

publishSelectedPathChange() is now called inside reserveControlOwner at both the fast-path (controlOwners[key] = ...; publishSelectedPathChange(); return) and the slow-path continuation branch. At that point sessions[key] is still nil — the session hasn't been established yet. selectedObservedPath() guards foregroundKey and controlKey with sessions[key] != nil, so the notification causes consumers to wake and read the old fallback path (or .unavailable) rather than the new foreground session's path. The session establishment fires a second notification that brings the correct value, so state is eventually consistent. The concern is that consumers will briefly observe either stale data from an older background session or .unavailable between control-owner registration and session connect, which could cause a visible flicker in connection-status UI. Consider deferring these two publishSelectedPathChange() calls to the moment the session is added to sessions (which already fires its own notification via the session(for:) path).

@azooz2003-bit azooz2003-bit closed this pull request by merging all changes into main in 288f1e1 Jul 19, 2026
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