Skip to content

irx: never stop attempting; cap every backoff at 30 minutes - #15450

Open
azooz2003-bit wants to merge 3 commits into
mainfrom
feat-v2-backoff-caps
Open

azooz2003-bit wants to merge 3 commits into
mainfrom
feat-v2-backoff-caps

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Directive from the 2026-09-28 NIGHTLY wedge (17h of silent relay-credential non-renewal, #15443): there must be no path that stops relay credential refreshing or connection attempts, and no backoff anywhere may exceed 30 minutes. The V2 control service had four stop-entirely classes:

  1. Terminal stop: one scopeMismatch, persistenceFailed (single failed disk write), capacityExceeded, invalidWireData (one malformed server frame), or any 1008/1009 close set status = .stopped, cleared the run, and nothing short of app relaunch or a manual Settings retry recovered. Even wake/foreground no-ops on a stopped run.
  2. Unbounded cooldowns: client_upgrade_required deferred a schema 1h → 6h → 24h; rate_limited and HTTP 429 honored uncapped server retry-afters; retryDelay inherited all of those through max().
  3. Renewal-loop exit: waking during the socket-gone window ended renewals for good while status stayed .ready.
  4. Activation retry floor: activationRetryDelay's ladder caps at 5 minutes, but an uncapped server retry-after floor could override it to hours.

What

  • terminal() stops the run only for deliberate revocation of this device's authority: server device_revoked/team_access_revoked, or a 1008 close carrying one of those reasons. Everything else retries on the reconnect ladder.
  • V2ControlService.maximumBackoff = 30min caps retryDelay, the upgrade-required ladder (now 10m/30m/30m), rate-limited cooldowns, and HTTP 429 cooldowns. Relay refresh failures keep their ~30s maintenance retry, far under the server's relay.request per-hour budget.
  • The renewal loop idles at a journaled 30s cadence (maintenance-idle) instead of exiting when the transport is momentarily gone.
  • activationRetryDelay clamps the retry-after floor at 30 minutes.

Rate-limit safety: worst case per schema stays ≥30s between attempts (maintenance failure cooldown), and reconnects keep exponential growth up to the ceiling, so a genuinely broken client asks the server at most ~2/min transiently and settles to ≥30s cadence, orders of magnitude under relay.request perHour(1200).

Testing

swift test in Packages/Shared/CmuxIrxTransport: 217 tests green. Spec-change tests updated and extended: onlyRevocationStopsEverythingElseRetriesWithinTheBackoffCeiling (revocation reasons stop; policy/wire/persistence/4xx do not; 6h retry-after and 24h accumulated cooldown both clamp to ≤1800s), retired-schema cooldown now asserts the 300-1800s bound, journal cooldown-set delay bound follows.

Stacked on #15443 (journal events used by the new idle path). Sibling: #15446 ships these events to Axiom, so maintenance-idle loops and capped cooldowns are visible in the sink.

Changelog

Fixed: a Mac could permanently stop renewing relay credentials or reconnecting after a transient failure; all connection and credential retries now continue at a bounded cadence and no backoff exceeds 30 minutes.

🤖 Generated with Claude Code


Summary by cubic

Prevents relay-credential renewal and reconnection from ever stopping permanently or backing off for more than 30 minutes, so a wedged Mac keeps announcing itself instead of going silent for hours.

  • terminal() now stops the run only for a deliberate revocation (device_revoked/team_access_revoked); wire-shape, persistence, policy, and 4xx failures all retry.
  • New app-wide 30-minute cap bounds the upgrade-required ladder, rate-limit cooldowns, HTTP 429 retry-afters, and the activation attempt floor.
  • The renewal loop idles at a 30s cadence when the transport is momentarily gone instead of exiting permanently.

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

Review in cubic

Summary by CodeRabbit

  • Reliability
    • The app now retries more connection and service failures instead of stopping, while access-revocation errors still stop the connection.
    • Retry delays and rate-limit cooldowns are capped at 30 minutes, including delays suggested by the server.
    • Maintenance now resumes attempts when the service is ready but temporarily lacks a connection.
    • Activation retries also respect the 30-minute maximum.
  • Diagnostics
    • Added event tracking for connection, credential, refresh, and maintenance activity to support troubleshooting.

azooz2003-bit and others added 2 commits September 28, 2026 14:18
Yesterday cmux NIGHTLY stopped renewing relay credentials at 03:00Z and
stayed unreachable from iOS for 17 hours without one log line saying why.
Every path that can stop renewals was unlogged; the 12h unified-log
retention then erased the failure window. This adds journal events at
each silent exit so the next wedge is attributable from retained logs:

- V2ControlService: session-ready, socket-failed, socket-open-failed,
  http-mode-entered, run-backing-off, run-stopped-terminal,
  maintenance-scheduled/-not-scheduled/-planned (with per-schema due
  times and cooldown deferrals)/-exited (with reason), refresh-succeeded
  and refresh-failed per schema, cooldown-set with source and delay, and
  persist-failed, all journaled from the service so a stalled snapshot
  consumer cannot hide them.
- MobileHostIrxRuntime: credentials-received, endpoint-ready-skipped
  with reason, and a renewal watchdog that reads the service directly
  every 5 minutes and journals credential-renewal-overdue and
  snapshot-apply-stalled (apply completion tracked via defer so a hang
  inside apply stays visible).
- IrxEndpoint/installer: relay-rotation-skipped/-deferred,
  relay-credential-install-started/-superseded, and
  relay-credential-unusable, making a hung native install visible as a
  started event with no outcome.

The journal is injected through V2ControlDependencies (defaulted nil)
and wired on both macOS and iOS. Events carry schema names, failure
codes, counts, and durations only, never tokens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 2026-09-28 NIGHTLY wedge showed a Mac silently stop renewing relay
credentials for 17 hours. Whatever the exact trigger was, the code had
four ways to stop attempting entirely, and none of them is acceptable:
a reachable Mac must keep announcing itself at a bounded cadence.

- terminal() now stops the run only for a deliberate revocation of this
  device's authority (server code or 1008 close reason). scopeMismatch,
  persistenceFailed, capacityExceeded, invalidWireData, non-revocation
  policy closes and 4xx responses all retry: one malformed frame or one
  failed disk write used to kill connectivity until app relaunch.
- V2ControlService.maximumBackoff (30 min) caps every delay: the
  client_upgrade_required ladder drops from 1h/6h/24h to 10m/30m/30m,
  rate-limited and HTTP 429 cooldowns honor the server retry-after only
  up to the ceiling, and retryDelay clamps whatever an accumulated
  cooldown says. Relay renewal retry stays ~30s per failure, far under
  the relay.request rate limit.
- The renewal loop no longer exits when it wakes during the brief
  socket-gone window; it idles at 30s (journaled as maintenance-idle)
  until the transport returns or the reconnect owner changes status.
- MobileHostIrxRuntime.activationRetryDelay: the server retry-after
  floor is capped at 30 minutes; the ladder already capped at 5.

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

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes add structured IRX lifecycle events, revise terminal and retry handling, cap retry and cooldown delays, and keep maintenance running when transport is unavailable. The mobile runtime adds a renewal watchdog and logs endpoint credential readiness and rotation events.

Changes

IRX renewal and retry reliability

Layer / File(s) Summary
V2 control journaling and retry policy
Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlDependencies.swift, Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlService*.swift, Packages/Shared/CmuxIrxTransport/Tests/CmuxIrxTransportTests/V2/V2ControlServiceTests.swift
V2 control accepts an optional journal and records connection and retry events. Only specified device and team revocation failures remain terminal. Retry and cooldown delays are capped at 30 minutes, and tests cover the revised policy.
Maintenance and refresh lifecycle
Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlService+Maintenance.swift, Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlService+Operations.swift, Packages/Shared/CmuxIrxTransport/Tests/CmuxIrxTransportTests/V2/V2ControlServiceTests.swift
Maintenance records scheduling, delay, and exit events. It waits and retries when no transport is available. Successful refreshes and maintenance failures record schema-related events.
Mobile credential rotation and renewal monitoring
Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/IrxEndpoint.swift, Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/IrxRelayCredentialInstaller.swift, Sources/Mobile/MobileHostIrxRuntime.swift, cmuxTests/MobileHostIrxSettingsMappingTests.swift, ios/cmuxPackage/Sources/cmuxFeature/MobileIrxRuntimeComposition+Lifecycle.swift
The mobile runtime passes its journal to V2 control and adds a five-minute renewal watchdog. Endpoint and installer paths record credential lifecycle events. Activation retry delays are capped at 30 minutes, with a test for a 24-hour server retry value.

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

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant MobileHostIrxRuntime
  participant V2ControlService
  participant IrxJournal
  MobileHostIrxRuntime->>V2ControlService: Provision with journal dependency
  V2ControlService->>IrxJournal: Record control and refresh events
  MobileHostIrxRuntime->>V2ControlService: Read snapshot during five-minute watchdog check
  MobileHostIrxRuntime->>IrxJournal: Record overdue renewal and unapplied snapshot events
Loading

Suggested reviewers: austinywang

✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

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

Copy link
Copy Markdown
Contributor

Dogfood tours of a876319b

sidebar-and-chrome-tour at a876319b: not run

skipped: CI built this head on a runner pool whose products the UI test Macs cannot load, and media never compiles one; gh workflow run pr-media.yml -f pr=&lt;n&gt; -f allow_compile=true does

Tours are picked by the paths globs in dogfood/scenarios/*.json; a Dogfood-tours: a, b line in the description picks them instead (none turns this off). Look at every frame before merging: a green tour only means no step failed.

Base automatically changed from feat-irx-renewal-observability to main September 29, 2026 04:00
@github-actions

Copy link
Copy Markdown
Contributor

Automatic catch-up couldn't merge main (ce40ebdd62f7): Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/IrxRelayCredentialInstaller.swift (both sides changed the same lines), Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlService+Maintenance.swift (both sides changed the same lines), Packages/Shared/CmuxIrxTransport/Sources/CmuxIrxTransport/V2/V2ControlService.swift (both sides changed the same lines), Packages/Shared/CmuxIrxTransport/Tests/CmuxIrxTransportTests/V2/V2ControlServiceTests.swift (both sides changed the same lines). Nothing was pushed; merge it by hand. A new push or /catch-up tries again.

Label no-auto-catch-up to opt out · Catch-up run

This branch has not been deployed

No deployments
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