Skip to content

Guarantee bounded iOS foreground reconnect - #9256

Closed
azooz2003-bit wants to merge 5 commits into
mainfrom
feat-reconnect-guarantee
Closed

azooz2003-bit wants to merge 5 commits into
mainfrom
feat-reconnect-guarantee

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

Regression structure

  • Commit 1 adds the failing CmuxIrohTransport regressions only.
  • Commit 2 lands the fix plus the rebased reconnect stack.

Test plan

  • swift test --package-path Packages/Shared/CmuxIrohTransport
  • swift test --package-path Packages/iOS/CmuxMobileRPC --filter MobileCoreRPCAbandonedConnectTests
  • swift test --package-path Packages/iOS/CmuxMobileRPC --filter MobileRPCTransportConnectEventTests
  • swift test --package-path Packages/Shared/CMUXMobileCore --filter DiagnosticLogTests
  • swift test --package-path Packages/iOS/CmuxMobileShell --filter MobileShellForegroundResumeTests
  • swift test --package-path Packages/iOS/CmuxMobileShell --filter MobilePresencePushRecoveryThrottleTests
  • swift test --package-path Packages/iOS/CmuxMobileShell --filter MobileShellWorkspaceCreateTests

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Guarantees a bounded iOS foreground reconnect by validating pooled Iroh sessions on wake, evicting dead/no-route sessions, and deferring recovery until the app is active to avoid background bounce. Adds grace-based path-closed eviction and clearer diagnostics so recovery is reliable and easy to reason about.

  • New Features

    • CmuxIrohTransport: Foreground validation evicts closed or .unavailable sessions before first reuse; adds 15s grace eviction for “all paths closed.”
    • CmuxMobileShell: Defers recovery triggers while inactive and replays one pending trigger on foreground; throttles presence-driven recovery (unchanged evidence at most every 45s); resets route-health gates on network changes.
    • CmuxMobileRPC: Surfaces connectAttemptGated when a route already has an in-flight dial; reports cancelled dial outcomes to avoid false failures; drops stale gates on network changes.
    • Diagnostics/UI: Adds routeGated failure kind, new lifecycle events (allPathsClosed, foregroundValidationFailed), and localized strings/UI for gated attempts; updates CmuxMobileShellUI and CmuxSettingsUI to display route-gated diagnostics.
  • Bug Fixes

    • CmuxIrohTransport: Correctly attributes display-format iroh closes (“timed out”, “closed”, “closed/aborted/reset by peer …”) with prefix-anchored parsing to prevent spoofing; corpse sessions are evicted; overlapping post-wake reconnects coalesce behind one replacement dial.
    • CMUXMobileCore: Cancelled dials no longer count as the latest failure in reports.
    • Reconnect flow: Selected-path change handling no longer leaves corpse sessions pooled; foreground recovery waits until active; tests updated and expanded, including path-closed eviction, wake validation, cancelled-outcome emission, presence throttle, and connect-timeout retry now handling routeCleanupBlocked.

Written for commit 5cbf238. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added clearer connection status reporting when another connection attempt is already in progress.
    • Improved recovery after network changes, backgrounding, and unavailable connection paths.
    • Added safeguards to prevent repeated recovery attempts during persistent outages.
    • Added diagnostic classifications for cancelled attempts, blocked routes, and closed paths.
  • Bug Fixes

    • Improved connection failure attribution, timeout handling, and session cleanup.
    • Prevented cancelled attempts from appearing as the latest failure when a real failure exists.
    • Added English and Japanese localized messages for new connection states.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds route-gated and session-lifecycle diagnostics, reports cancelled attempts, improves pooled-session eviction and close attribution, and coordinates mobile recovery during foreground and presence changes.

Changes

Connection recovery and diagnostics

Layer / File(s) Summary
Diagnostic contracts and presentation
Packages/Shared/CMUXMobileCore/..., Packages/macOS/CmuxSettingsUI/..., Packages/iOS/CmuxMobileShellUI/..., Resources/Localizable.xcstrings, Packages/iOS/CmuxMobileShellUI/...
Adds route-gated and session-lifecycle classifications. Cancelled outcomes no longer replace real failures.
Pooled session validation and path eviction
Packages/Shared/CmuxIrohTransport/...
Validates pooled sessions on activation and evicts sessions after bounded path unavailability.
Connection-close attribution
Packages/Shared/CmuxIrohTransport/...
Classifies Iroh and Quinn display causes and extracts peer application error codes.

Mobile connection admission and recovery

Layer / File(s) Summary
Connection admission and cancellation reporting
Packages/iOS/CmuxMobileRPC/...
Distinguishes cleanup blocking from active route contention and records cancelled connection attempts.
Foreground and presence recovery coordination
Packages/iOS/CmuxMobileShell/..., ios/cmux/Resources/Localizable.xcstrings
Defers recovery while foreground refresh is inactive and throttles unchanged presence evidence for 45 seconds.
Connection-gated error handling
Packages/iOS/CmuxMobileShell/...
Maps connectAttemptGated across pairing, tasks, workspace actions, availability, and artifact classification.

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

Possibly related issues

  • manaflow-ai/cmux-dev-artifacts#7014 — The cancellation diagnostic changes are related to the reported MobileCoreRPCSession.swift concurrency compilation failure.
  • manaflow-ai/cmux#9166 — This PR resets route health on network changes and adds connectAttemptGated.
  • manaflow-ai/cmux#9169 — This PR updates Iroh and Quinn connection-close attribution.
  • manaflow-ai/cmux#9177 — This PR adds unchanged presence-heartbeat recovery throttling.

Sequence Diagram(s)

sequenceDiagram
  participant MobileShellComposite
  participant MobileRPCConnectAttemptRegistry
  participant MobileCoreRPCSession
  participant CmxIrohClientSessionPool
  participant DiagnosticLog

  MobileShellComposite->>MobileRPCConnectAttemptRegistry: request route admission
  MobileRPCConnectAttemptRegistry-->>MobileCoreRPCSession: allow or return connectAttemptGated
  MobileCoreRPCSession->>CmxIrohClientSessionPool: create or reuse connection
  CmxIrohClientSessionPool-->>MobileCoreRPCSession: report path or close state
  MobileCoreRPCSession->>DiagnosticLog: record connected, cancelled, or gated outcome
Loading
🚥 Pre-merge checks | ✅ 19 | ❌ 6

❌ Failed checks (1 warning, 5 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux No Hacky Sleeps ❓ Inconclusive Custom check execution failed before a final verdict was produced. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Cmux Swift Concurrency ❓ Inconclusive Custom check execution failed before a final verdict was produced. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Cmux Swift @Concurrent ❓ Inconclusive Custom check execution failed before a final verdict was produced. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Cmux Swift Package Boundaries ❓ Inconclusive Custom check execution failed before a final verdict was produced. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Cmux User-Facing Error Privacy ❓ Inconclusive Custom check execution failed before a final verdict was produced. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (19 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 All production Swift changes maintain proper actor isolation: value types are Sendable, service types are actors, UI types are @MainActor, and background tasks explicitly re-enter main actor. No is...
Cmux Swift Blocking Runtime ✅ Passed Production code uses only async-await patterns (clock.sleep with async protocol, ContinuousClock().sleep) and boolean state transitions; no blocking primitives (Semaphore, Thread.sleep, Task.sleep)...
Cmux Browser Automation Off-Main ✅ Passed PR modifies only transport/connectivity and diagnostics files (35 files total). The browser automation rule applies only to TerminalController.swift and ControlCommandExecutionPolicy.swift; neither...
Cmux Expensive Synchronous Load ✅ Passed PR adds no expensive synchronous loads (RestorableAgentSessionIndex.load, FileManager, JSON parsing) to @MainActor or interactive paths. Changes are connection recovery logic, session pool validati...
Cmux Cache Substitution Correctness ✅ Passed The diff does not replace an authoritative read in a persistence, history, undo, or snapshot path; pooled sessions are transient and new foreground/path checks re-read live state.
Cmux Algorithmic Complexity ✅ Passed All production code changes respect algorithmic complexity rules: session pools are tiny fixed-size collections (1-3 per peer), string parsing is per-connection not a scan, network route reset is s...
Cmux Swiftpm Lockfiles ✅ Passed The PR diff changes only source, tests, and localization files; it changes no Package.swift, Package.resolved, .gitignore, Xcode project/workspace, or workflow files.
Cmux Swift Logging ✅ Passed The base-to-tip Swift diff adds no print, debugPrint, dump, NSLog, file/stdout logging, or Logger declarations; it only adds in-memory DiagnosticLog test helpers and removes one existing log call.
Cmux Full Internationalization ✅ Passed All new user-facing strings are properly localized: three new localization keys added to string catalogs with complete en/ja translations matching each catalog's existing pattern; all Swift code us...
Cmux Swiftui State Layout ✅ Passed MobileShellComposite uses modern @Observable pattern with value-type state properties (no @Published). Settings UI views use immutable snapshots for list rows. No new ObservableObject or GeometryRe...
Cmux Architecture Rethink ✅ Passed PR introduces properly-owned correctness fixes without timing repairs: foregroundRefreshIsActive defers recovery logically (no sleep/poll), presencePushRecoveryThrottle is a rate-limiter gate, allP...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR modifies diagnostic display in existing SwiftUI Views (IrohNetworkingSection, MobileIrohSettingsView) with no new NSWindow, NSPanel, NSWindowController, or WindowGroup definitions. The determini...
Cmux Source Artifacts ✅ Passed All 35 changed files are legitimate hand-written Swift source, test files, and localization catalogs in appropriate package directories; no artifact patterns violated.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No test or debug seams found. All #if DEBUG blocks gate real product behavior or are pre-existing. New public APIs (resetRouteHealthForNetworkChange, connectAttemptGated) are production features wi...
Cmux No Ambient Global State ✅ Passed No ambient global state violations detected. All new API is either enum cases in existing types, instance methods on injectable structs, or instance methods on existing types—never file-scope funct...
Title check ✅ Passed The title clearly summarizes the primary change: bounded foreground reconnect behavior on iOS.
Description check ✅ Passed The description provides a detailed summary and test plan, but it omits the template’s demo video, review trigger, and checklist sections.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-reconnect-guarantee

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.

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

🤖 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/MobileRPCTransportConnectEventTests.swift`:
- Around line 101-119: Update the test’s retry sequencing around firstTask and
waitUntilFirstConnectFinished so the retry starts only after
transportConnectObserver receives and signals the cancelled .failed event from
reportCancelledConnect. Await that real observer completion signal before
initiating the second connection, preserving the expected attempt, failed,
attempt, connected event order.

In
`@Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/IrohNetworkingSection.swift`:
- Around line 601-605: Add localized string-unit translations for
settings.networking.diagnostics.failure.routeGated in the existing
Localizable.xcstrings catalog, covering every supported locale and preserving
the English default value “Connection Attempt Held.”

In `@Resources/Localizable.xcstrings`:
- Around line 172910-172926: Add localized entries for
settings.networking.diagnostics.failure.routeGated in every supported catalog
locale missing from its localizations block: ar, bs, da, de, es, fr, it, km, ko,
nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant. Preserve the existing en
and ja translations and use the same translated stringUnit structure for each
locale.
🪄 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 Plus

Run ID: 17477ae6-e321-4f0b-be78-ed0bbcbe674b

📥 Commits

Reviewing files that changed from the base of the PR and between 3d8e32b and 9dbeec4.

📒 Files selected for processing (33)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticReport.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticTaxonomy.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift
  • Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohClientSessionPool.swift
  • Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohConnectionCloseAttribution.swift
  • Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientSessionPoolPathEvictionTests.swift
  • Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohClientSessionPoolTests.swift
  • Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohConnectionCloseAttributionTests.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession+RequestSettlement.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileRPCConnectAttemptRegistry.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileShellConnectionError.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileCoreRPCAbandonedConnectTests.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobileRPCTransportConnectEventTests.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileChatEventSource.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobilePairingFailure.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobilePresencePushRecoveryThrottle.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ConnectionRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PresenceRouteSync.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ReconnectRoutes.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskDirectoryList.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceActions.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellMacAvailabilityFailureClassifier.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/SecondaryControlAttemptPolicy.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/WorkspaceCreatePinnedContext.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobilePresencePushRecoveryThrottleTests.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellForegroundResumeTests.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellWorkspaceCreateTests.swift
  • Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/IrohNetworkingSection.swift
  • Resources/Localizable.xcstrings
  • ios/cmux/Resources/Localizable.xcstrings

Comment on lines 101 to 119
continuation.finish()
let recorded = await collect(events)
#expect(recorded.count == 3)
guard recorded.count == 3 else {
#expect(recorded.count == 4)
guard recorded.count == 4 else {
await session.tearDown(error: .connectionClosed)
return
}
guard case let .attempt(firstAttemptID, firstTransport) = recorded[0],
case let .attempt(secondAttemptID, secondTransport) = recorded[1],
case let .connected(connectedID, connectedTransport, _) = recorded[2] else {
Issue.record("Expected attempt, attempt, connected with no failure")
guard case let .attempt(firstAttemptID, _) = recorded[0],
case let .failed(abandonedID, abandonedTransport, abandonedFailure, _) = recorded[1],
case let .attempt(secondAttemptID, _) = recorded[2],
case let .connected(connectedID, _, _) = recorded[3] else {
Issue.record("Expected attempt, failed(cancelled), attempt, connected")
await session.tearDown(error: .connectionClosed)
return
}
#expect(firstAttemptID > 0)
#expect(secondAttemptID > 0)
#expect(firstTransport == .debugLoopback)
#expect(secondTransport == .debugLoopback)
#expect(abandonedID == firstAttemptID)
#expect(abandonedTransport == .debugLoopback)
#expect(abandonedFailure == .cancelled)
#expect(connectedID == secondAttemptID)

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 | 🟡 Minor | ⚡ Quick win

Wait for the cancelled event before starting the retry.

Line 103 requires the cancelled failure to precede the retry attempt. firstTask.value and waitUntilFirstConnectFinished() can complete before reportCancelledConnect() emits the failure event. The retry can then emit its attempt first.

Signal from transportConnectObserver when it receives .failed(..., .cancelled, ...). Start the retry only after that signal. As per coding guidelines, tests must await real completion signals rather than rely on scheduling order.

🤖 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/Tests/CmuxMobileRPCTests/MobileRPCTransportConnectEventTests.swift`
around lines 101 - 119, Update the test’s retry sequencing around firstTask and
waitUntilFirstConnectFinished so the retry starts only after
transportConnectObserver receives and signals the cancelled .failed event from
reportCancelledConnect. Await that real observer completion signal before
initiating the second connection, preserving the expected attempt, failed,
attempt, connected event order.

Source: Coding guidelines

Comment on lines +601 to +605
case .some(.routeGated):
String(
localized: "settings.networking.diagnostics.failure.routeGated",
defaultValue: "Connection Attempt Held"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

key='settings.networking.diagnostics.failure.routeGated'
found=0

while IFS= read -r -d '' file; do
  if rg -n -q "\"${key}\"" "$file"; then
    found=1
    printf '\n%s\n' "$file"
    rg -n -C 4 "\"${key}\"" "$file"
  fi
done < <(fd --type f --extension xcstrings --print0 .)

test "$found" -eq 1

Repository: manaflow-ai/cmux

Length of output: 207


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

key='settings.networking.diagnostics.failure.routeGated'

printf 'Matching code references:\n'
rg -n "\"${key}\"" "$PWD" || true

printf '\nxcstrings files containing diagnostics.failure entries:\n'
while IFS= read -r -d '' file; do
  if rg -q 'diagnostics\.failure' "$file"; then
    printf '%s\n' "$file"
  fi
done < <(find "$PWD" -type f -name '*.xcstrings' -print0 | sort -z)

printf '\nAll xcstrings entries for the key if any:\n'
python3 - <<'PY'
import json, pathlib
key='settings.networking.diagnostics.failure.routeGated'
for p in sorted(pathlib.Path('.').glob('**/*.xcstrings')):
    data = json.loads(p.read_text(encoding='utf-8'))
    strings = (data.get('strings') or {})
    if key in strings:
        loc = p.parent.name or '<root>'
        print(loc, p)
        entry = strings[key]
        print('entry:', json.dumps(entry, ensure_ascii=False, indent=2))
    else:
        # search nested strings dictionaries just in case
        def find_strings(d):
            if isinstance(d, dict):
                if 'strings' in d:
                    yield d['strings']
                for v in d.values():
                    yield from find_strings(v)
        hits = []
        for strings in find_strings(data):
            if key in strings:
                hits.append(strings[key])
        if hits:
            print('unknown nested', p)
            print(json.dumps(hits, ensure_ascii=False, indent=2))
PY

Repository: manaflow-ai/cmux

Length of output: 602


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

key='settings.networking.diagnostics.failure.routeGated'

printf 'Matching code references:\n'
rg -n "\"${key}\"" "$PWD" || true

printf '\nxcstrings files containing diagnostics.failure entries:\n'
while IFS= read -r -d '' file; do
  if rg -q 'diagnostics\.failure' "$file"; then
    printf '%s\n' "$file"
  fi
done < <(find "$PWD" -type f -name '*.xcstrings' -print0 | sort -z)

printf '\nAll xcstrings entries for the key if any:\n'
python3 - <<'PY'
import json, pathlib
key='settings.networking.diagnostics.failure.routeGated'
for p in sorted(pathlib.Path('.').glob('**/*.xcstrings')):
    data = json.loads(p.read_text(encoding='utf-8'))
    strings = (data.get('strings') or {})
    if key in strings:
        loc = p.parent.name or '<root>'
        print(loc, p)
        entry = strings[key]
        print('entry:', json.dumps(entry, ensure_ascii=False, indent=2))
    else:
        def find_strings(d):
            if isinstance(d, dict):
                if 'strings' in d:
                    yield d['strings']
                for v in d.values():
                    yield from find_strings(v)
        hits = []
        for strings in find_strings(data):
            if key in strings:
                hits.append(strings[key])
        if hits:
            print('unknown nested', p)
            print(json.dumps(hits, ensure_ascii=False, indent=2))
PY

Repository: manaflow-ai/cmux

Length of output: 602


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
import json, pathlib

key='settings.networking.diagnostics.failure.routeGated'
catalog='Resources/Localizable.xcstrings'

data = json.loads(pathlib.Path(catalog).read_text(encoding='utf-8'))
entry = (data.get('strings') or {}).get(key)
if not entry:
    print('Key not found in strings dictionary')
    raise SystemExit(1)

print('baseValue:', entry.get('baseValue'))
translations = entry.get('translations') or {}
print('translation_key_count:', len(translations))
for lang, trans in sorted(translations.items()):
    print(f'{lang}: language={trans.get("language")} region={trans.get("region")} strings={len(trans.get("stringUnit", {}).get("value", ""))} chars={len(trans.get("stringUnit", {}).get("value", ""))}')

print('\nAll non-empty translation values:')
for lang, trans in sorted(translations.items()):
    value = trans.get('stringUnit', {}).get('value', '')
    print(f'{lang}: {value}')
PY

Repository: manaflow-ai/cmux

Length of output: 230


Add catalog translations for settings.networking.diagnostics.failure.routeGated.

Resources/Localizable.xcstrings defines the key, but it has no translations entries, so this user-facing text will only fall back to the English default. Add per-locale string-unit values for every supported locale.

🤖 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/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/IrohNetworkingSection.swift`
around lines 601 - 605, Add localized string-unit translations for
settings.networking.diagnostics.failure.routeGated in the existing
Localizable.xcstrings catalog, covering every supported locale and preserving
the English default value “Connection Attempt Held.”

Source: Path instructions

Comment on lines +172910 to +172926
"settings.networking.diagnostics.failure.routeGated": {
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "Connection Attempt Held"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "接続試行が一時的に保留されました"
}
}
}
},

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

python3 - <<'PY'
import json

with open("Resources/Localizable.xcstrings", encoding="utf-8") as f:
    catalog = json.load(f)

key = "settings.networking.diagnostics.failure.routeGated"
locales = catalog["strings"][key]["localizations"]
print("Locales for key:", sorted(locales))

all_locales = set()
for entry in catalog["strings"].values():
    all_locales.update(entry.get("localizations", {}))
print("Catalog locales:", sorted(all_locales))
PY

Repository: manaflow-ai/cmux

Length of output: 335


Add translations for all catalog locales.

settings.networking.diagnostics.failure.routeGated only defines en and ja, while Resources/Localizable.xcstrings supports ar, bs, da, de, en, es, fr, it, ja, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant. Add matching translations for the missing locales to prevent fallback text.

🤖 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 `@Resources/Localizable.xcstrings` around lines 172910 - 172926, Add localized
entries for settings.networking.diagnostics.failure.routeGated in every
supported catalog locale missing from its localizations block: ar, bs, da, de,
es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, and zh-Hant.
Preserve the existing en and ja translations and use the same translated
stringUnit structure for each locale.

Sources: Path instructions, Learnings

azooz2003-bit and others added 2 commits July 30, 2026 22:43
The PR reports a pending abandoned-connect cleanup as routeCleanupBlocked
instead of a fake requestTimedOut. The retry loop in
connectTimeoutDoesNotPoisonLaterRetryOnSameClient still only retried on
requestTimedOut, so it surfaced the new error as a test failure. Retry on
both, matching the updated assertions elsewhere in this file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Closeout verification (taken over from the original agent session).

What this PR implements against the wake-reconnect goal

Disposition of overlapping PRs

Branch state

  • Merges cleanly onto current origin/main (git merge-tree clean, GitHub MERGEABLE/CLEAN). No merge commit needed.
  • Pushed 5cbf238: one-line test fix. connectTimeoutDoesNotPoisonLaterRetryOnSameClient's retry loop still caught only requestTimedOut, but this PR intentionally reports pending abandoned-connect cleanup as routeCleanupBlocked, so the suite failed on head. The fix retries on both, matching the assertions this PR already updated elsewhere in the same file. Runtime code untouched.

Test results (branch head, local)

  • swift test --package-path Packages/Shared/CmuxIrohTransport: 509 tests / 63 suites, all pass.
  • Packages/iOS/CmuxMobileRPC full: 171 tests / 24 suites pass (AbandonedConnect suite re-run 3x green after the fix above).
  • Packages/Shared/CMUXMobileCore full: 318 tests / 26 suites pass.
  • Packages/iOS/CmuxMobileShell targeted (MobileShellForegroundResumeTests, MobilePresencePushRecoveryThrottleTests, MobileShellWorkspaceCreateTests): pass.

Red/green proof (throwaway worktree of origin/main 541fe7f, only the new test file copied over)

  • sameGenerationWakeEvictsSilentIdleDeathBeforeFirstReuse: FAILS on main with 3 issues (post-wake session identical to the dead pooled session, no second dial, dead session never closed). Passes on the branch.
  • overlappingPostWakeReconnectsCoalesceBehindOneReplacementDial: FAILS on main with 4 issues. Passes on the branch.
  • CmxIrohClientSessionPoolPathEvictionTests only compile-fails on main (references the new allPathsClosed API), so its red proof is compile-level, weaker than behavioral.

Hosted CI (speculative merge onto main, ci.yml on gate/feat-reconnect-guarantee-1785489816, run 30620430986)

  • swift-package-tests, app-host shards 1–3, release-build, linux-preflight, web-typecheck etc: green.
  • Only failure: app-host shard 4, AgentNotificationMutationBoundaryTests ("Live PID routing ... Dock-owned terminal"). The identical test fails on unrelated gate runs 30624146240 and 30624108678 (different PRs), so it is a pre-existing main-side flake, not from this branch.

CodeRabbit findings: the two localization findings are false positives (its script read a translations key; xcstrings uses localizations — all three new keys ship en+ja). The MobileRPCTransportConnectEventTests event-ordering race is real but minor; suite passed 5/5 runs here.

Simulator behavioral evidence: NOT YET CAPTURED. The tagged macOS app is rebuilt, launched, socket verified, and a simulator attach ticket mints (MINT_OK). The iOS build itself is blocked tonight: the single fleet Mac (cmux-aws-m4pro) sits below the 12GB --min-free-gb lease floor so every cloud iOS build spins (2h+ waited), Blacksmith cannot produce the sim/device artifacts for this path, and local Xcode builds on this machine are livelocked behind 5h-old concurrent agent builds. A local --simulator-only build and a re-queued fleet device build (offline-queue park for the phone) are still running; the wake->pairOk <= 3s decode (event 52 a:1 -> event 2 from the diagnostic archive) will be posted as a follow-up comment when a build lands. Do not merge on my account until that lands or a maintainer accepts the unit-level evidence above.

azooz2003-bit added a commit that referenced this pull request Aug 1, 2026
Recovery triggers (network change, presence push, liveness, dead event
stream) that arrive while the iOS scene is inactive or mid-backgrounding
used to dial immediately. The dial suspends with the process (field
traces on the reconnect incident showed ~9.5s stalls) and later competes
with the foreground recovery pass. Park the trigger in
pendingInactiveRecoveryTrigger while foregroundRefreshIsActive is false
and replay the most recent one exactly once in resumeForegroundRefresh(),
after the foreground passes, so the replay coalesces into any attempt
they already started.

An explicit pairing connect and the account boundary clear the parked
trigger, matching how they supersede live recovery.

Green for the regression added in the previous commit. Ports the
inactive-parking piece of #9256
onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Aug 1, 2026
…imedOut

When the connect-attempt registry refuses a dial because the exact route
already has a connect attempt in flight (.busy), the session threw
requestTimedOut. The refusal is instantaneous and never reached the
network, so diagnostics recorded fabricated sub-30ms "timedOut"
failures that poisoned lastFailureEvent and made exports look like the
network was timing out during recovery storms.

Add MobileShellConnectionError.connectAttemptGated with a dedicated
DiagnosticFailureKind.routeGated (raw value 25, append-only) and throw
it for the .busy gate. Callers keep their previous user-facing behavior
(retryable timeout category); only the diagnostic taxonomy and the
settings diagnostics rows distinguish the gate refusal. New localized
strings (en/ja) for the error and both diagnostics surfaces.

Single commit: the regression tests reference the new enum cases, so a
tests-first commit cannot compile against main. Tests:
- activeRouteAdmissionReportsRouteGatedInsteadOfTimedOut (RPC): a second
  session on a route with an in-flight dial gets connectAttemptGated and
  never allocates a transport.
- gatedDialRefusalsReportRouteGatedNotTimedOut (CMUXMobileCore): a gated
  refusal surfaces as routeGated in lastFailureKind, never timedOut.
- Taxonomy raw-value and diagnosticFailureKind mapping expectations.

Ports the truth-telling piece of
#9256 onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Aug 1, 2026
…r 45s

The iOS presence subscription delivers ~15s heartbeats about online
Macs. While the phone is disconnected, every heartbeat restarted
connection recovery through recoverFromPushedRouteBatch, so during a
persistent outage the phone kept abandoning its own in-flight dials on
the heartbeat cadence and each abandoned dial fed the connect-registry
gate (#9177).

Connectivity v2 did not absorb this: CmxConnectivityInvalidationSubscriber
and ConnectivityInvalidationSubscriberCoordinator replaced the Mac-side
PresenceNudgeSubscriber, while the phone-side presence path
(PresenceClient -> syncPushedRoutes -> recoverFromPushedRouteBatch ->
recoverMobileConnection(.presencePush)) survives unthrottled on main.

MobilePresencePushRecoveryThrottle passes changed evidence (new routes,
a Mac coming online) unconditionally and unchanged heartbeats at most
once per 45s, above the heartbeat cadence and a recovery pass's dial
budget, below the 30-60s automatic backoff ladder. Clock is injected
per call (runtime?.now()); a rewound wall clock re-admits instead of
freezing. Account boundary resets the throttle.

Single commit: the tests reference the new type, so a tests-first
commit cannot compile against main. Ports the throttle piece of
#9256 onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit added a commit that referenced this pull request Aug 1, 2026
* Test: recovery triggers fired while inactive must wait for the foreground probe

A recovery trigger arriving while the iOS scene is inactive or mid-
backgrounding must not dial: the dial suspends with the process (field
traces on the reconnect incident showed ~9.5s stalls) and then competes
with the foreground recovery pass. Expect no probe until
resumeForegroundRefresh(), then exactly one.

Red on current main; the parking fix lands in the next commit.
Ports the regression from #9256
onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Park inactive-phase recovery triggers and replay them on foreground

Recovery triggers (network change, presence push, liveness, dead event
stream) that arrive while the iOS scene is inactive or mid-backgrounding
used to dial immediately. The dial suspends with the process (field
traces on the reconnect incident showed ~9.5s stalls) and later competes
with the foreground recovery pass. Park the trigger in
pendingInactiveRecoveryTrigger while foregroundRefreshIsActive is false
and replay the most recent one exactly once in resumeForegroundRefresh(),
after the foreground passes, so the replay coalesces into any attempt
they already started.

An explicit pairing connect and the account boundary clear the parked
trigger, matching how they supersede live recovery.

Green for the regression added in the previous commit. Ports the
inactive-parking piece of #9256
onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Classify connect-registry gate refusals as connectAttemptGated, not timedOut

When the connect-attempt registry refuses a dial because the exact route
already has a connect attempt in flight (.busy), the session threw
requestTimedOut. The refusal is instantaneous and never reached the
network, so diagnostics recorded fabricated sub-30ms "timedOut"
failures that poisoned lastFailureEvent and made exports look like the
network was timing out during recovery storms.

Add MobileShellConnectionError.connectAttemptGated with a dedicated
DiagnosticFailureKind.routeGated (raw value 25, append-only) and throw
it for the .busy gate. Callers keep their previous user-facing behavior
(retryable timeout category); only the diagnostic taxonomy and the
settings diagnostics rows distinguish the gate refusal. New localized
strings (en/ja) for the error and both diagnostics surfaces.

Single commit: the regression tests reference the new enum cases, so a
tests-first commit cannot compile against main. Tests:
- activeRouteAdmissionReportsRouteGatedInsteadOfTimedOut (RPC): a second
  session on a route with an in-flight dial gets connectAttemptGated and
  never allocates a transport.
- gatedDialRefusalsReportRouteGatedNotTimedOut (CMUXMobileCore): a gated
  refusal surfaces as routeGated in lastFailureKind, never timedOut.
- Taxonomy raw-value and diagnosticFailureKind mapping expectations.

Ports the truth-telling piece of
#9256 onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Throttle unchanged-evidence presence-push recovery restarts to one per 45s

The iOS presence subscription delivers ~15s heartbeats about online
Macs. While the phone is disconnected, every heartbeat restarted
connection recovery through recoverFromPushedRouteBatch, so during a
persistent outage the phone kept abandoning its own in-flight dials on
the heartbeat cadence and each abandoned dial fed the connect-registry
gate (#9177).

Connectivity v2 did not absorb this: CmxConnectivityInvalidationSubscriber
and ConnectivityInvalidationSubscriberCoordinator replaced the Mac-side
PresenceNudgeSubscriber, while the phone-side presence path
(PresenceClient -> syncPushedRoutes -> recoverFromPushedRouteBatch ->
recoverMobileConnection(.presencePush)) survives unthrottled on main.

MobilePresencePushRecoveryThrottle passes changed evidence (new routes,
a Mac coming online) unconditionally and unchanged heartbeats at most
once per 45s, above the heartbeat cadence and a recovery pass's dial
budget, below the 30-60s automatic backoff ladder. Clock is injected
per call (runtime?.now()); a rewound wall clock re-admits instead of
freezing. Account boundary resets the throttle.

Single commit: the tests reference the new type, so a tests-first
commit cannot compile against main. Ports the throttle piece of
#9256 onto connectivity v2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Superseded, all content accounted for: the wake-time dead-session validation/eviction and dial coalescing shipped with connectivity v2 on main (deadOnArrivalSessionIsClosedAndRedialedOnce, unavailableSelectedPathEvictsTheSessionAndTheNextOperationRedials); the three pieces v2 did not pick up (scene-phase recovery parking, connectAttemptGated truth-telling, presence-push recovery throttle) merged via #9347 (798aa34); the shared backoff policy merged via #9301 (c3cdbd6); the 15s all-paths-closed grace eviction is being carried into v2 by #9241. Still absent on main from this branch: cancelled-dial .cancelled reporting with the lastFailureEvent filter, and the session-level requestTimedOut→routeCleanupBlocked admission reclassification — follow-up candidates noted in 9347's PR body.

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