Skip to content

Recover the Mac iroh host runtime from terminal failure without relaunch - #8930

Closed
azooz2003-bit wants to merge 1 commit into
mainfrom
feat-iroh-failed-recovery
Closed

azooz2003-bit wants to merge 1 commit into
mainfrom
feat-iroh-failed-recovery

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Problem

CmxIrohHostRuntime fails closed on any non-transient trust-broker rejection (401/403/404/409, malformed responses): it tears the endpoint down into a terminal .failed lifecycle phase. That posture is correct, but recovery was owned by nobody. MobileHostIrohRuntime.retryIfNeeded() returned early whenever it still held a runtime reference (only re-syncing LAN publication), and a failed cold-start activation had no retry timer at all. Result: one rejected registration left the Mac unregistered until sign-out/sign-in, a Settings-triggered iroh restart, or an app relaunch. This is the client half of the App Store review incident, where a stale binding produced a 409 and the review Mac sat unreachable for 17 hours.

#8883 and #8888 remove the main 409 trigger (binding slot re-key). This PR removes the wedge class itself, for every non-transient rejection, including future ones.

Mechanism

Recovery is owned by the macOS composition root and is level-triggered through the existing reconcile path (no new mutation path):

  • Every failed activation (reconcile catch) and every runtime self-teardown into .failed (observed via the existing handleDeactivation callback, filtered by lifecycle revision so deliberate stops are ignored) arms one pending rebuild with bounded exponential backoff: 30s doubling to a 1h cap, jittered, using the same CmxIrohRetrySchedule and injected-clock idiom as the runtime's internal retries.
  • retryIfNeeded() now checks the held runtime's snapshot: a .failed runtime is rebuilt immediately on any external wake signal (network path change via MobileHostService, app-level retry), resetting the backoff ladder.
  • Each reconcile cancels the pending attempt on entry and re-derives recovery from its own outcome: success resets the ladder, failure re-arms it, sign-out/deactivation ends it. At most one attempt is ever pending, and stale wake-ups are no-ops.

Principled or hacky: principled. The inner runtime's fail-closed teardown is untouched (a rejected binding never keeps accepting connections), .failed was already designed restartable (LifecyclePhase.allowsStart), and the rebuild goes through the same reconcile path used by launch, sign-in, and Settings. Residual risk: a persistently rejecting broker now sees one registration attempt per backoff interval (capped at hourly) instead of silence; the challenge/register round trip is cheap and rate-limit floors (Retry-After) are honored by the schedule.

Tests

CmxIrohHostRuntimeFailedRestartTests pins the package contract the watchdog depends on: a 409-rejected registration refresh fails closed (endpoint closed once, deactivation notified once, snapshot/phase .failed) and the same runtime accepts start() again once the broker allows registration, ending active. If .failed ever stops allowing start(), this fails instead of silently reverting to the relaunch-only wedge.

Two-commit red/green does not apply: the wedge lives in app-target singleton wiring (MobileHostIrohRuntime + MobileHostService + auth) with no practical automated harness, so the package test guards the enabling semantics and the app wiring is covered by build plus the dogfood path below. All 36 CmxIrohHostRuntime* package tests pass. No user-facing strings were added (log lines only), so no localization changes are needed.

Verification

  • swift test --filter CmxIrohHostRuntime in Packages/Shared/CmuxIrohTransport: 36/36 pass.
  • Tagged fleet build irecov from this head.

🤖 Generated with Claude Code


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

Automatically recover the Mac iroh host runtime after terminal failures so a non-transient broker rejection no longer leaves the host unregistered until relaunch. Recovery is level-triggered via the existing reconcile path with bounded backoff.

  • Bug Fixes
    • Recovery is owned in the macOS composition root; rebuilds trigger on failed activation or .failed deactivation.
    • Uses exponential backoff (30s → 1h cap) with jitter via CmxIrohRetrySchedule; only one attempt pending.
    • retryIfNeeded() immediately rebuilds a .failed runtime on external wake signals and resets backoff; each reconcile cancels pending recovery and re-derives from outcome.
    • handleDeactivation now arms recovery (filtered by lifecycleRevision); added CmxIrohHostRuntimeFailedRestartTests to verify 409 rejection fails closed and later start() succeeds.

Written for commit cb412f4. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved recovery when host runtime activation or broker registration fails.
    • Failed runtimes now retry automatically with controlled backoff and jitter.
    • Prevented duplicate recovery attempts and preserved active account state during restarts.
    • Successful reactivation now clears prior failure history.
  • Tests

    • Added coverage verifying failed runtimes can be restarted and return to an active state.

A non-transient broker rejection (401/403/404/409, invalid response)
tears CmxIrohHostRuntime down into a terminal .failed phase. That
fail-closed teardown is deliberate, but nothing ever rebuilt the
runtime: MobileHostIrohRuntime.retryIfNeeded() only re-synced LAN
publication while it held a runtime reference, and no timer retried a
failed activation. A Mac whose registration was rejected once stayed
unregistered until sign-out/sign-in, a Settings-triggered restart, or
an app relaunch (the 17-hour App Store review 409 wedge).

Recovery is now owned by the macOS composition root, level-triggered
through the existing reconcile path:

- Every failed activation and every runtime self-teardown into .failed
  (reported through the existing handleDeactivation callback, filtered
  by lifecycle revision so deliberate stops are ignored) arms one
  pending rebuild with bounded exponential backoff (30s doubling to a
  1h cap, jittered, via CmxIrohRetrySchedule and an injected clock).
- retryIfNeeded() now rebuilds a .failed runtime immediately on any
  external wake signal (network path change, app-level retry) and
  resets the backoff ladder, instead of only re-syncing LAN state.
- Each reconcile cancels the pending attempt and re-derives recovery
  from its own outcome: success resets the ladder, failure re-arms it,
  sign-out/deactivation ends it.

The new package test pins the contract this depends on: a rejected
registration refresh fails closed (endpoint torn down, deactivation
notified) and the same runtime accepts start() again once the broker
allows registration. The two-commit red/green structure does not apply
because the wedge lives in app-target singleton wiring that has no
practical automated harness; the package test guards the enabling
semantics instead.

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

coderabbitai Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

MobileHostIrohRuntime now detects terminal runtime failures, schedules guarded exponential-backoff recovery, and restarts failed active runtimes. Activation deactivation reports failure state, and new tests verify failed teardown followed by successful restart.

Changes

Runtime failure recovery

Layer / File(s) Summary
Recovery state and reconciliation
Sources/Mobile/MobileHostIrohRuntime.swift
Adds recovery task, backoff, clock, and scheduling state; manages recovery during reconciliation, successful activation, and activation errors.
Failed deactivation detection and retry routing
Sources/Mobile/MobileHostIrohRuntime+Activation.swift, Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift
Reports failed runtime deactivation, applies guarded backoff recovery, restarts failed runtimes, and preserves LAN synchronization for non-failed runtimes.
Failed restart test coverage
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohHostRuntimeFailedRestartTests.swift
Verifies rejected registration causes failed teardown and that a subsequent start restores the runtime to active.

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

Sequence Diagram(s)

sequenceDiagram
  participant HostRuntimeTest
  participant CmxIrohHostRuntime
  participant Broker
  participant MobileHostIrohRuntime
  HostRuntimeTest->>CmxIrohHostRuntime: start()
  CmxIrohHostRuntime->>Broker: refresh registration
  Broker-->>CmxIrohHostRuntime: 409 binding replacement rejection
  CmxIrohHostRuntime-->>MobileHostIrohRuntime: deactivate failed runtime
  MobileHostIrohRuntime->>MobileHostIrohRuntime: schedule failure recovery
  MobileHostIrohRuntime->>CmxIrohHostRuntime: restart active runtime
  CmxIrohHostRuntime->>Broker: register again
  Broker-->>CmxIrohHostRuntime: successful registration
  CmxIrohHostRuntime-->>HostRuntimeTest: active lifecycle phase
Loading

Possibly related PRs


Important

Pre-merge checks failed

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

❌ Failed checks (2 errors)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error Production scheduleFailureRecovery() adds a sleep-backed retry timer in Sources/Mobile/..., which the rule treats as disallowed timing-based synchronization. Move recovery off clock.sleep in the runtime and onto a dedicated cancellation-aware scheduler/notification/state-transition path; keep polling only in tests.
Cmux Architecture Rethink ❌ Error retryIfNeeded() still contains a direct failed-runtime restart branch instead of reusing the shared recovery helper, leaving duplicate restart wiring. Route the .failed runtime wake path through recoverFailedRuntimeIfNeeded() (or equivalent shared helper) so there is one restart entrypoint and one set of guards.
✅ Passed checks (23 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: recovering the Mac iroh host runtime from terminal failure without relaunch.
Description check ✅ Passed The description covers the problem, mechanism, tests, and verification, but it omits template sections like Demo Video, Review Trigger, and Checklist.
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 production changes stay on @MainActor and use explicit Task { @mainactor }/MainActor.run; the new clock/schedule types are Sendable, and the test actor is allowed.
Cmux Browser Automation Off-Main ✅ Passed PR only changes Mobile iroh runtime recovery and a transport test; no browser-automation commands/routing or worker-lane WebKit/AppKit waits are touched.
Cmux Expensive Synchronous Load ✅ Passed The diff only adds runtime recovery logic and tests; no new agent-history/jsonl loaders or main-actor interactive file scans appear in touched code.
Cmux Cache Substitution Correctness ✅ Passed No production diff swaps a fresh read for an unguarded cache: runtime.snapshot() reads actor-owned currentSnapshot, and cached stores validate scope/expiry/binding before reuse.
Cmux No Hacky Sleeps ✅ Passed PASS: The diff only touches Swift files; no TypeScript/JS/shell/build/runtime script sleeps or polling were added. The only polling is a bounded test helper.
Cmux Algorithmic Complexity ✅ Passed The PR’s production changes add only control-flow and task scheduling; no new nested scans, repeated sorts/filters, or per-target rescans on scalable collections.
Cmux Swift Concurrency ✅ Passed No new legacy async patterns: the diff uses structured Tasks/actors only, adds no DispatchQueue/Combine/completion-handler APIs, and the recovery task is stored/cancelled.
Cmux Swift @Concurrent ✅ Passed No touched Swift code adds/changes @concurrent or nonisolated async isolation; new async hops use @MainActor tasks and actor calls explicitly.
Cmux Swift Package Boundaries ✅ Passed PASS: The new logic stays in Sources/Mobile as macOS composition-root lifecycle/recovery glue; reusable runtime/policy mechanics remain in CmuxIrohTransport.
Cmux Swiftpm Lockfiles ✅ Passed PR only changes Swift source/test files; no .gitignore, Package.resolved, Xcode project, or workflow dependency edits are present.
Cmux Swift Logging ✅ Passed Only new production logging is a Logger-based error message; no print/debugPrint/NSLog/file stdout logging or sensitive data exposure was added.
Cmux User-Facing Error Privacy ✅ Passed No user-facing errors, alerts, or API copy were added; the only new strings are internal logs and test-only assertions/comments.
Cmux Full Internationalization ✅ Passed Changed files add only debug logs, comments, and tests; no new user-facing localized text or catalog/message edits were introduced.
Cmux Swiftui State Layout ✅ Passed The PR only touches runtime/test classes, not SwiftUI views, and adds no ObservableObject/@Published/GeometryReader/lazy-row or render-time state writes.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR only touches runtime/test Swift files; no NSWindow/NSPanel/WindowGroup/NSWindowController or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed Changed paths are hand-written Swift source/test files; no artifact, cache, build, or temp directories were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR only adds recovery state/logic in production sources; no new debug/test-only members or wrappers were introduced, and the existing #if DEBUG helpers were unchanged.
Cmux No Ambient Global State ✅ Passed PASS: the diff adds only instance-scoped failure-recovery state/methods on existing MobileHostIrohRuntime; no new file-scope funcs/vars or singleton surfaces were introduced.
✨ 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-failed-recovery

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 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds automatic recovery for terminally failed macOS Iroh host runtimes.

  • Adds bounded exponential-backoff rebuild scheduling at the macOS composition root.
  • Rebuilds failed held runtimes immediately when an external retry signal arrives.
  • Uses lifecycle revisions to suppress recovery after deliberate deactivation.
  • Adds a package test proving a failed host runtime can be started again after broker rejection.

Confidence Score: 3/5

This PR should not merge until stale deactivation cleanup is prevented from disrupting a newly rebuilt Iroh host runtime.

Immediate external-wake recovery can overlap the failed runtime’s asynchronous callback, whose unguarded global cleanup may close the replacement runtime’s connections and clear its published binding.

Files Needing Attention: Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift and Sources/Mobile/MobileHostIrohRuntime+Activation.swift

Important Files Changed

Filename Overview
Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift Adds retry scheduling and immediate failed-runtime rebuilding, but permits replacement activation to overlap stale destructive cleanup.
Sources/Mobile/MobileHostIrohRuntime+Activation.swift Extends deactivation handling with recovery notification, while retaining global cleanup before lifecycle revision validation.
Sources/Mobile/MobileHostIrohRuntime.swift Adds recovery task state and integrates cancellation, backoff reset, and failure re-arming into reconciliation.
Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohHostRuntimeFailedRestartTests.swift Verifies fail-closed teardown and successful restart of the same runtime after broker recovery.

Sequence Diagram

sequenceDiagram
  participant Old as Failed runtime
  participant Callback as Deactivation callback
  participant Wake as External retry signal
  participant Root as MobileHostIrohRuntime
  participant New as Replacement runtime
  Old->>Callback: Begin deactivation
  Callback->>Callback: await LAN publisher stop
  Wake->>Root: retryIfNeeded()
  Root->>New: Reconcile and activate
  New-->>Root: Active
  Callback->>Root: Close all connections and clear binding
  Note over Root,New: Stale cleanup disrupts replacement
  Callback->>Root: Revision check rejects only recovery scheduling
Loading

Reviews (1): Last reviewed commit: "Recover the Mac iroh host runtime from t..." | Re-trigger Greptile

Comment on lines +278 to +282
self.cancelFailureRecovery(resetBackoff: true)
self.scheduleReconcile(
eraseAccountState: false,
restartActiveRuntime: true
)

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 Stale cleanup disrupts replacement runtime

When an external retry starts replacement activation while the failed runtime’s deactivation callback is suspended, the old callback resumes and unconditionally stops LAN publication, closes all Iroh connections, and clears the service binding before checking its lifecycle revision, causing the rebuilt host to become unreachable again without arming recovery for the new runtime.

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

Knowledge Base Used: Shared Packages (Packages/Shared/)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

Inline comments:
In `@Sources/Mobile/MobileHostIrohRuntime.swift`:
- Around line 120-123: Make failureRecoveryClock and failureRecoverySchedule in
MobileHostIrohRuntime test-injectable by changing them from immutable concrete
defaults to internal mutable properties, or by accepting them through an
initializer while preserving the existing defaults. Ensure tests can substitute
a virtual clock and deterministic retry schedule via `@testable` import without
changing production behavior.

In `@Sources/Mobile/MobileHostIrohRuntime`+Lifecycle.swift:
- Around line 261-289: Update retryIfNeeded() to delegate failed-runtime
handling to recoverFailedRuntimeIfNeeded() instead of performing the inline
cancelFailureRecovery and scheduleReconcile restart flow. Preserve the existing
active-runtime and lifecycleRevision guards, and let the shared helper enforce
its transitionTask == nil guard and backoff reset behavior.
🪄 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: fbfdb702-bd89-47da-962e-ebfd6246a5fb

📥 Commits

Reviewing files that changed from the base of the PR and between da8a58a and cb412f4.

📒 Files selected for processing (4)
  • Packages/Shared/CmuxIrohTransport/Tests/CmuxIrohTransportTests/CmxIrohHostRuntimeFailedRestartTests.swift
  • Sources/Mobile/MobileHostIrohRuntime+Activation.swift
  • Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift
  • Sources/Mobile/MobileHostIrohRuntime.swift

Comment on lines +120 to +123
var failureRecoveryTask: Task<Void, Never>?
var failureRecoveryFailureCount = 0
let failureRecoveryClock: any CmxIrohRelayClock = CmxIrohSystemRelayClock()
let failureRecoverySchedule = CmxIrohRetrySchedule()

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 | 🔵 Trivial | ⚡ Quick win

Backoff clock/schedule aren't test-injectable.

failureRecoveryClock and failureRecoverySchedule are let-bound to concrete defaults with no initializer seam, so the jittered-backoff timing logic in this composition-root file (unlike the CmxIrohHostRuntime layer, which has dedicated tests per the PR) can't be exercised deterministically from a test target via @testable import. Consider widening these to internal var (or accepting them via init) so a test can substitute a virtual clock/deterministic schedule, consistent with how other clock-dependent code in this codebase is tested.

🤖 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 `@Sources/Mobile/MobileHostIrohRuntime.swift` around lines 120 - 123, Make
failureRecoveryClock and failureRecoverySchedule in MobileHostIrohRuntime
test-injectable by changing them from immutable concrete defaults to internal
mutable properties, or by accepting them through an initializer while preserving
the existing defaults. Ensure tests can substitute a virtual clock and
deterministic retry schedule via `@testable` import without changing production
behavior.

Comment on lines +261 to +289
guard let activeRuntime = runtime else {
scheduleReconcile(eraseAccountState: false)
return
}
let revision = lifecycleRevision
Task { @MainActor [weak self] in
guard let self,
self.desiredActive,
self.runtime === activeRuntime,
revision == self.lifecycleRevision else { return }
if await activeRuntime.snapshot().state == .failed {
guard self.desiredActive,
!self.signOutIntentActive,
self.runtime === activeRuntime,
revision == self.lifecycleRevision else { return }
await self.synchronizeLANPublicationWithSettings()
// A fresh external signal rebuilds a failed runtime now and
// restarts the recovery backoff ladder.
self.cancelFailureRecovery(resetBackoff: true)
self.scheduleReconcile(
eraseAccountState: false,
restartActiveRuntime: true
)
return
}
guard self.runtime === activeRuntime,
revision == self.lifecycleRevision else { return }
await self.synchronizeLANPublicationWithSettings()
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find file and inspect relevant sections"
fd -a 'MobileHostIrohRuntime\+Lifecycle\.swift$' . || true
file="$(fd 'MobileHostIrohRuntime\+Lifecycle\.swift$' . | head -n 1)"
if [ -z "${file:-}" ]; then
  echo "file not found"
  exit 0
fi
echo "FILE=$file"
wc -l "$file"
echo "--- lines 220-295 ---"
sed -n '220,295p' "$file" | nl -ba -v220
echo "--- lines 310-365 ---"
sed -n '310,365p' "$file" | nl -ba -v310
echo "--- retryIfNeeded/recoverFailed callers/usages ---"
rg -n "retryIfNeeded|recoverFailedRuntimeIfNeeded|scheduleReconcile\\(.*restartActiveRuntime|cancelFailureRecovery\\(resetBackoff: true\\)" "$file" . --glob '!**/.git/**' --glob '!**/DerivedData/**' --glob '!**/build/**' | head -n 200

Repository: manaflow-ai/cmux

Length of output: 448


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect relevant declarations and surrounding state"
file="$(fd 'MobileHostIrohRuntime\+Lifecycle\.swift$' . | head -n 1)"
echo "--- lines 1-220 ---"
sed -n '1,220p' "$file" | nl -ba -v1
echo "--- all occurrences of methods/properties relevant to race evidence ---"
rg -n "retryIfNeeded|recoverFailedRuntimeIfNeeded|scheduleReconcile|cancelFailureRecovery|transitionTask|lifecycleRevision|desiredActive|signOutIntentActive|desiredActive|runtime\s*=" "$file" -C 2

Repository: manaflow-ai/cmux

Length of output: 266


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="./Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift"
echo "FILE=$file"
wc -l "$file"
echo "--- lines 220-295 ---"
sed -n '220,295p' "$file" | awk '{printf "%5s\t%s\n", NR+219, $0}'
echo "--- lines 310-365 ---"
sed -n '310,365p' "$file" | awk '{printf "%5s\t%s\n", NR+309, $0}'
echo "--- lines 1-180 ---"
sed -n '1,180p' "$file" | awk '{printf "%5s\t%s\n", NR, $0}'
echo "--- all occurrences relevant to lifecycle/transition/backoff/schedule ---"
rg -n "retryIfNeeded|recoverFailedRuntimeIfNeeded|scheduleReconcile|cancelFailureRecovery|transitionTask|lifecycleRevision|desiredActive|signOutIntentActive|runtime\s*=|desiredActive\s*=" "$file" -C 2

Repository: manaflow-ai/cmux

Length of output: 21770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Find caller contexts"
rg -n "retryIfNeeded\\(|recoverFailedRuntimeIfNeeded\\(" . --glob '!**/.git/**' --glob '!**/DerivedData/**' --glob '!**/build/**' --glob '!**/.build/**' | head -n 200

Repository: manaflow-ai/cmux

Length of output: 770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "--- MobileHostService.swift around retryIfNeeded call ---"
sed -n '1660,1725p' ./Sources/Mobile/MobileHostService.swift | awk '{printf "%5s\t%s\n", NR+1659, $0}'

echo "--- MobileHostIrohRuntime+Lifecycle.swift scheduleFailureRecovery and cancel/backoff area ---"
sed -n '290,378p' ./Sources/Mobile/MobileHostIrohRuntime+Lifecycle.swift | awk '{printf "%5s\t%s\n", NR+289, $0}'

echo "--- MobileHostIrohRuntime+Lifecycle.swift network observation / callback references around setDesiredActive and shared observation fields ---"
rg -n "setDesiredActive|retryIfNeeded|update|Network|network|path|paths|Observing|Observation|Observable|`@Observable`|IrohNetwork|TransportVerification|CmxIrohTransportVerificationMode" ./Sources/Mobile ./cmuxTests --glob '!**/.git/**' --glob '!**/DerivedData/**' | head -n 250

echo "--- Swift availability of async Task cancellation behavior probe (no repo code) ---"
cat > /tmp/task_cancellation_probe.swift <<'SWIFT'
`@available`(macOS 14.0, macCatalyst 17.0, iOS 17.0, visionOS 1.0, watchOS 10.0, tvOS 17.0, *)
func probe() async {
  let startedLock = Lock()
  startedLock.lock()
  var started = false
  let task = Task {
    startedLock.lock()
    started = true
    startedLock.unlock()
    try await Task.sleep(nanoseconds: 2_000_000_000)
  }
  startedLock.wait()
  task.cancel()
  try? await task.value
  print(started)
}
SWIFT
swiftc -typecheck /tmp/task_cancellation_probe.swift 2>&1 | sed -n '1,80p' || true

Repository: manaflow-ai/cmux

Length of output: 39971


Use the shared failed-runtime recovery path instead of duplicating it.

retryIfNeeded() is still invoked from network path changes, so its .failed runtime branch can run while recovery backoff is pending. The inline scheduleReconcile(eraseAccountState: false, restartActiveRuntime: true) also skips the post-snapshot() transitionTask == nil guard that recoverFailedRuntimeIfNeeded() keeps, creating a second restart entrypoint that can race. Delegate to recoverFailedRuntimeIfNeeded() from here; that helper already restarts the backoff ladder when a failed runtime is rebuilt.

🤖 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 `@Sources/Mobile/MobileHostIrohRuntime`+Lifecycle.swift around lines 261 - 289,
Update retryIfNeeded() to delegate failed-runtime handling to
recoverFailedRuntimeIfNeeded() instead of performing the inline
cancelFailureRecovery and scheduleReconcile restart flow. Preserve the existing
active-runtime and lifecycleRevision guards, and let the shared helper enforce
its transitionTask == nil guard and backoff reset behavior.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants