Skip to content

Fix iOS render-grid input replay flake - #6953

Closed
austinywang wants to merge 36 commits into
mainfrom
issue-5911-ios-ci-rendergridterminalinputwaitsforliveeve
Closed

austinywang wants to merge 36 commits into
mainfrom
issue-5911-ios-ci-rendergridterminalinputwaitsforliveeve

Conversation

@austinywang

@austinywang austinywang commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #5911

Summary

  • add a regression for input catch-up when a render-grid re-subscribe repairs a lost host registration
  • replay affected mounted surfaces when input_seq_wait discovers the subscription was just reinstalled
  • make renderGridTerminalInputWaitsForLiveEventBeforeReplay wait on collector delivery instead of a sleep polling window

Validation

  • swift test --filter inputSeqWaitRepairingLostSubscriptionReplaysMountedSurface (failed before fix, passed after)
  • swift test --filter MobileShellRenderGridLivenessTests
  • swift test --filter renderGridTerminalInputWaitsForLiveEventBeforeReplay could not run locally because SwiftPM reported local binary target GhosttyKit at /Users/cmux/manaflow/term/cmux133/GhosttyKit.xcframework does not contain a binary artifact

Localization

  • No user-facing strings changed.

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


Summary by cubic

Fixes #5911. Repaired terminal event re-subscribe now reliably replays all mounted render‑grid surfaces, supersedes any in‑flight cold‑attach replay, and ignores duplicate pending ACKs to avoid unnecessary resyncs.

  • Bug Fixes

    • refreshTerminalEventSubscription(reason:, replaySurfaceIDsIfRepaired:) uses the ack; when a lost registration is repaired it calls replayAfterRepairedTerminalEventSubscription(...) to replay the given surfaces (or all mounted) and refresh workspaces.
    • replayAfterRepairedTerminalEventSubscription cancels any in‑flight replay before requesting the repaired catch‑up and passes resolved workspace IDs to avoid re‑scans.
    • input_seq_wait requests a repaired replay for all mounted surfaces; if a surface is still behind after setting a pending seq, trigger a resync; duplicate pending ACKs no longer restart the event stream.
    • iOS UI: gate glass/scroll‑edge APIs by compiler (MobileGlassEffectContainer, View+MobileGlass), mark key‑command creation and transcript/table builders @MainActor, add nonisolated inits, and move push permission reads to a nonisolated helper.
  • Tests

    • Added regressions for: single‑surface repair, superseding an in‑flight cold‑attach replay, global multi‑surface repair, repaired empty‑list refresh that also refetches workspaces, and ignoring duplicate pending ACKs.
    • Test router: acks include workspace/surface IDs and seq; supports per‑surface deterministic mobile.terminal.replay, delayed subscribe acks, terminal.input responses, and terminal.bytes gaps.
    • TerminalOutputCollector.waitForLineCount now fails fast with a timeout.

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

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal event subscription repair to use acknowledgement state, replay the right mounted terminal surfaces for repaired input, and refresh workspace data.
    • Updated render-grid liveness recovery so repaired-input catch-up is delivered consistently across all mounted terminal output surfaces.
  • Tests
    • Added render-grid liveness regression coverage for repaired subscription and input_seq_wait scenarios.
    • Improved terminal output test utilities for deterministic line-count waiting.
  • Documentation / Compatibility
    • Updated iOS “glass” and iOS 26 scroll-edge styling to compile only with Swift 6.2+.
  • Chores
    • Main-actor isolated keyboard dismissal tap UI handling.

@vercel

vercel Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 5, 2026 2:43am
cmux-staging Building Building Preview, Comment Jul 5, 2026 2:43am

@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR updates terminal subscription repair so repaired subscriptions replay mounted surfaces and refresh workspace state. Test support now tracks surface_id through replay plumbing, liveness coverage adds repaired-subscription cases with deterministic waiting, and mobile UI helpers gate Liquid Glass paths on Swift compiler 6.2.

Changes

Terminal subscription repair and replay

Layer / File(s) Summary
Subscription repair flow
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
refreshTerminalEventSubscription now inspects the subscribe ack and, when a repaired subscription was newly installed, replays provided surfaces and refreshes the workspace list; the repair call sites pass mounted output sinks into that flow.
Surface-aware transport
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
RecordedRequest, router counts, replay state, and LivenessTransport.send(_:) now carry surface_id through request recording and response dispatch, including per-surface replay frames and render-grid-compatible terminal responses.
Deterministic wait helper
ios/cmuxPackage/Tests/cmuxFeatureTests/cmuxFeatureTests.swift
TerminalOutputCollector adds line-count waiters, and the render-grid input test now waits directly for two collected lines.
Repair replay tests
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift
The render-grid liveness tests add repaired-subscription coverage for one mounted surface, for all mounted surfaces after input is submitted, and for the default refresh repair path.

Mobile UI compatibility guards

Layer / File(s) Summary
Glass helper gates
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/View+MobileGlass.swift
All mobile glass styling helpers now compile only when building for iOS with compiler version 6.2 or later.
Accessory and scroll-edge gates
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift, Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/ChatScrollEdgeCoordinator.swift, Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/ChatTranscriptTableView.swift
TerminalInputTextView updates its accessory-button documentation and wraps the iOS 26 Liquid Glass configuration in a compiler availability guard, while ChatScrollEdgeCoordinator and ChatTranscriptTableView gate their scroll-edge styling paths behind the same compiler version check.
Main-actor dismissal tap
Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/KeyboardDismissTap.swift
KeyboardDismissTap annotates TapInstallerView with @MainActor.

Sequence Diagram(s)

sequenceDiagram
  participant MobileShellComposite
  participant subscribeRpc as mobile.events.subscribe
  participant replayHelper as replayAfterRepairedTerminalEventSubscription
  participant workspaceRefresh as scheduleWorkspaceListRefreshFromEvent

  MobileShellComposite->>subscribeRpc: refreshTerminalEventSubscription(reason, replaySurfaceIDsIfRepaired)
  subscribeRpc-->>MobileShellComposite: ack.alreadySubscribed
  alt alreadySubscribed == false
    MobileShellComposite->>replayHelper: replay mounted surfaceIDs
    replayHelper->>workspaceRefresh: schedule refresh
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • manaflow-ai/cmux#5869: Both PRs modify MobileShellComposite repair logic around already_subscribed ack handling and catch-up replay for mounted terminal surfaces.

Suggested reviewers

  • lawrencecchen
  • jesstelford

Poem

A bunny found sync in the terminal glow,
The repaired little replay began to flow.
Two hops for the surfaces, one whisker for time,
And the workspace came back in a neat little rhyme.
🐇✨


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error MobileShellComposite now batch-replays all mounted surfaces on repaired subscribe; each replay does workspaceID(forTerminalID:), which scans all workspaces and terminals. Cache terminal→workspace IDs or pass workspace IDs into replay so the repaired-subscription loop is one-pass instead of rescanning per surface.
Out of Scope Changes check ⚠️ Warning The PR includes unrelated compiler-gating, @MainActor, and accessibility text changes beyond the #5911 flake fix. Split the unrelated iOS 26/compiler-guard and accessibility updates into separate PRs, keeping this change focused on the render-grid replay flake fix.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (22 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes address #5911 by replacing polling with deterministic collector synchronization and adding regression coverage for the flake.
Cmux Swift Actor Isolation ✅ Passed The only production isolation change adds @MainActor to SwiftUI rendering helpers inside a UIRepresentable; no new background access or Sendable debt was introduced.
Cmux Swift Blocking Runtime ✅ Passed Blocking primitives appear only in test scaffolding; production diffs are async/event-driven guards and compiler guards, with no new semaphores/sleeps/polls.
Cmux Browser Automation Off-Main ✅ Passed PR only changes iOS/mobile shell, test support, and UI files; no browser.* routing, WebKit wait handling, or control-socket policy code was touched.
Cmux Expensive Synchronous Load ✅ Passed Touched production Swift only adds replay/subscribe wiring and compiler guards; no agent-history loads or large file parsing were added to main-actor/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed PASS: The diff doesn’t replace any authoritative persistence/history/undo/snapshot read with a cache; the replay fallback is event-driven and non-persisted.
Cmux No Hacky Sleeps ✅ Passed PASS: PR only touches Swift app/test code; no non-Swift runtime scripts or new fixed delays/polling were introduced, and the test change replaces polling with a continuation.
Cmux Swift Concurrency ✅ Passed Diff adds checked continuations and stored task handles in test helpers; no new DispatchQueue, Combine, completion-handler, or unowned fire-and-forget app/runtime tasks.
Cmux Swift @Concurrent ✅ Passed No changed function adds or misuses @concurrent; the touched async helpers stay actor/UI-bound, and the patch is limited to replay/test refactors plus compile guards.
Cmux Swift File And Package Boundaries ✅ Passed Touched oversized files only incidentally; production edits are small UI/bug-fix guards or one focused helper (+38/-10 in a 6.9k-line file), and test-only changes are exempt.
Cmux Swiftpm Lockfiles ✅ Passed PR diff vs origin/main only changes Swift source/test files; no Package.swift, Package.resolved, .gitignore, or xcodeproj metadata changed.
Cmux Swift Logging ✅ Passed No banned print/NSLog/debugPrint/dump were added; the new diagnostic uses existing MobileDebugLog.anchormux and only logs sanitized reason/count data.
Cmux User-Facing Error Privacy ✅ Passed Checked touched production files and user-facing literals; new copy is generic (e.g. no vendor/internal details) and no rule-violating error text was added.
Cmux Full Internationalization ✅ Passed The only code changes are @MainActor and String(line.dropFirst()) coercions; no new user-facing copy was added, and the referenced localization keys already exist with en/ja entries.
Cmux Swiftui State Layout ✅ Passed Touched SwiftUI code keeps snapshot/closure rows and compile guards only; no new ObservableObject/@published, GeometryReader layout, or render-time state writes.
Cmux Architecture Rethink ✅ Passed Production fix uses subscribe ack to decide replay with clear owner/invariant; polling/continuations are test-only scaffolding.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed This diff only tweaks chat transcript/table-row SwiftUI views; it adds no NSWindow/NSPanel/WindowGroup code, no cmux.* window IDs, and no close-shortcut routing.
Cmux Source Artifacts ✅ Passed Only two Swift source files changed; no logs, caches, temp dirs, build output, or other artifact paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug-only accessor was added in production Sources; the PR changes internal replay/liveness behavior, while test helpers live in Tests/ or pre-existed.
Cmux No Ambient Global State ✅ Passed Production changes stay inside types/private helpers; no new global mutable state, singleton, or ambient API surface was added.
Title check ✅ Passed The title matches the main change: fixing the iOS render-grid input replay flake.
Description check ✅ Passed The description covers summary, validation, and localization; only non-critical template sections like demo video and checklist are 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 issue-5911-ios-ci-rendergridterminalinputwaitsforliveeve

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 Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes #5911: iOS render-grid input replay stall when input_seq_wait discovers the host-side subscription was lost. The core change is refreshTerminalEventSubscription(reason:replaySurfaceIDsIfRepaired:) now checks the subscribe ack and, when the host reports already_subscribed: false, calls replayAfterRepairedTerminalEventSubscription to cancel any in-flight cold-attach replay and issue a fresh catch-up for every affected mounted surface.

  • replayAfterRepairedTerminalEventSubscription pre-builds a workspaceID map from current workspace state to avoid per-surface re-scans, cancels the in-flight coalescing guard before each requestTerminalReplay, and calls scheduleWorkspaceListRefreshFromEvent to recover missed workspace mutations.
  • input_seq_wait now always passes all currently mounted surfaces to the refresh, ensuring the global registration repair replays every surface rather than only the one that sent input.
  • iOS glass/scroll-edge APIs are gated behind #if compiler(>=6.2), and key actor-isolation fixes (@MainActor, nonisolated init, nonisolated helpers) address Swift 6 diagnostics across several iOS UI files.

Confidence Score: 5/5

Safe to merge; the repair logic is well-bounded and all four race-condition paths are covered by new regression tests.

The core subscription-repair change is mechanically sound: it checks the ack after the subscribe returns, uses the captured surface list to avoid re-scan races, cancels the coalescing guard before each replay, and then refetches workspace state. The didMoveToWindow cleanup path fully covers the removed deinit in TapInstallerView. The compiler-version guards around iOS 26 glass APIs are the correct way to avoid build failures on older SDKs. No unhandled concurrency hazards or data-loss paths were identified.

No files require special attention.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Core fix: adds replaySurfaceIDsIfRepaired param to refreshTerminalEventSubscription, extracts replayAfterRepairedTerminalEventSubscription that cancels in-flight replays and replays all affected surfaces, and ensures input_seq_wait passes all mounted surfaces to the repaired path.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift Adds four regression tests covering: single-surface input repair, superseding in-flight cold-attach replay, global multi-surface repair, and repaired empty-list refresh that also refetches workspaces.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift Test router extended with per-surface replay frames, delayed subscribe support, surface ID tracking in recorded requests, and richer terminal.input ACKs with workspace/seq metadata.
ios/cmuxPackage/Tests/cmuxFeatureTests/cmuxFeatureTests.swift Adds TerminalOutputCollector.waitForLineCount with a timeout continuation, replacing a 200-iteration sleep loop; unmount resumes pending waiters gracefully.
Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/KeyboardDismissTap.swift Adds @mainactor to TapInstallerView (satisfies Swift 6 isolation); removes the deinit gesture-recognizer cleanup because didMoveToWindow fully owns that lifecycle.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileGlassEffectContainer.swift New wrapper view that gates GlassEffectContainer behind #if compiler(>=6.2), replacing scattered inline availability checks.
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/View+MobileGlass.swift Adds compiler(>=6.2) to all six #if os(iOS) guards that reference iOS 26 glass/scroll-edge APIs, fixing compilation on older SDKs.
Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalHardwareKeyResolver.swift Adds @mainactor to makeKeyCommands(target:action:) which accesses UIKeyCommand (main-actor-bound UIKit API).
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePushCoordinator.swift Extracts currentAuthorizationStatus() as a nonisolated async helper so the UNUserNotificationCenter read does not require main actor isolation on its caller.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App as iOS App
    participant MSC as MobileShellComposite
    participant Sub as refreshTerminalEventSubscription
    participant Mac as Mac Host

    App->>MSC: submitTerminalRawInput(surfaceID)
    MSC->>MSC: "input_seq_wait detected (localSeq < remoteSeq)"
    MSC->>Sub: refreshTerminalEventSubscription(reason: input_seq_wait, replaySurfaceIDsIfRepaired: [all mounted])
    Sub->>Mac: requestTerminalEventSubscription(topics)
    Mac-->>Sub: "ack { already_subscribed: false }"
    Note over Sub: Registration was lost - repair path
    Sub->>MSC: replayAfterRepairedTerminalEventSubscription(surfaceIDs: [all mounted])
    loop For each mounted surface
        MSC->>MSC: cancelTerminalReplayInFlight(surfaceID)
        MSC->>Mac: requestTerminalReplay(surfaceID, resolvedWorkspaceID)
        Mac-->>MSC: replay frame with post-gap seq
        MSC->>App: deliver catch-up output
    end
    MSC->>Mac: scheduleWorkspaceListRefreshFromEvent()
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"}}}%%
sequenceDiagram
    participant App as iOS App
    participant MSC as MobileShellComposite
    participant Sub as refreshTerminalEventSubscription
    participant Mac as Mac Host

    App->>MSC: submitTerminalRawInput(surfaceID)
    MSC->>MSC: "input_seq_wait detected (localSeq < remoteSeq)"
    MSC->>Sub: refreshTerminalEventSubscription(reason: input_seq_wait, replaySurfaceIDsIfRepaired: [all mounted])
    Sub->>Mac: requestTerminalEventSubscription(topics)
    Mac-->>Sub: "ack { already_subscribed: false }"
    Note over Sub: Registration was lost - repair path
    Sub->>MSC: replayAfterRepairedTerminalEventSubscription(surfaceIDs: [all mounted])
    loop For each mounted surface
        MSC->>MSC: cancelTerminalReplayInFlight(surfaceID)
        MSC->>Mac: requestTerminalReplay(surfaceID, resolvedWorkspaceID)
        Mac-->>MSC: replay frame with post-gap seq
        MSC->>App: deliver catch-up output
    end
    MSC->>Mac: scheduleWorkspaceListRefreshFromEvent()
Loading

Reviews (29): Last reviewed commit: "Avoid render-grid resync on pending dupl..." | Re-trigger Greptile

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 5749-5753: The repaired-subscription flow in
MobileShellComposite’s subscription refresh path is incorrectly gated by
replaySurfaceIDsIfRepaired and an early return on repaired acks, which can block
default callers and lose a later input_seq_wait repair replay. Update the logic
in the subscription handling method so alreadySubscribed == false is handled
first, then replay the current/provided mounted surfaces, and always enqueue the
workspace refresh regardless of whether the replay list is empty. Also ensure
terminalSubscriptionRefreshTask does not suppress the follow-up refresh request
needed after a repaired ack.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c060037-cde0-4442-bbc4-8704accc2d05

📥 Commits

Reviewing files that changed from the base of the PR and between 58830db and 217a11a.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTests.swift
  • ios/cmuxPackage/Tests/cmuxFeatureTests/cmuxFeatureTests.swift

…dergridterminalinputwaitsforliveeve

# Conflicts:
#	.github/swift-file-length-budget.tsv
austinywang and others added 3 commits July 1, 2026 17:24
…dergridterminalinputwaitsforliveeve

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/ChatScrollEdgeCoordinator.swift
#	Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/ChatTranscriptTableView.swift
#	Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
#	Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
…h replay

Regression coverage for the race autoreview flagged in
replayAfterRepairedTerminalEventSubscription: when a cold-attach replay
for a surface is still in flight, requestTerminalReplay coalesces (no-ops
on its in-flight guard), so the repaired-subscription catch-up is silently
dropped and the surface stays behind the input response sequence with no
follow-up replay.

This test parks the mount replay in flight, then drives an input_seq_wait
repair for the same surface and asserts a second replay is issued and the
post-gap frame is delivered. It fails against the current coalescing
behavior (commit 1 of the two-commit red/green pair); the superseding fix
follows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
replayAfterRepairedTerminalEventSubscription issued requestTerminalReplay
per mounted surface, but that helper is a no-op while a replay for the
surface is already in flight. A repaired subscription is precisely the
catch-up after the host reported the registration was absent, so coalescing
behind an older in-flight replay (e.g. a cold-attach replay that started
before the gap) silently dropped the catch-up: the older replay returns a
snapshot from before the missed events, pendingTerminalByteEndSeqBySurfaceID
stays behind the input response sequence, and nothing re-triggers a replay.

Cancel any in-flight replay for the surface before requesting the catch-up
so the fresh request always goes out. The subsequent requestTerminalReplay
re-adopts the active replay barrier token (if any), preserving barrier
semantics; the cancelled request's completion is dropped by its stale
request-id guard, so there is no retry storm.

Fixes the race covered by the preceding test (commit 2 of the two-commit
red/green pair).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 18 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated
Comment thread ios/cmuxPackage/Tests/cmuxFeatureTests/cmuxFeatureTests.swift Outdated
Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated
austinywang and others added 3 commits July 1, 2026 19:31
The two-commit regression pair grew two tracked files past their recorded
budgets: the test file (MobileShellRenderGridLivenessTests.swift +54, the new
repairReplaySupersedesInFlightColdAttachReplay case) and the fix
(MobileShellComposite.swift +9, the supersede guard). Re-record both so the
swift-file-length-budget guard (workflow-guard-tests -> ci-status) passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An earlier commit replaced `isolated deinit` with
`deinit { MainActor.assumeIsolated { … } }` for older-compiler
compatibility, but `assumeIsolated` traps if the final
`MobileShellComposite` release happens off the main actor — turning an
ordinary lifecycle edge into a crash.

Gate the safe, actually actor-isolated `isolated deinit` behind
`#if compiler(>=6.2)`. CI's iOS-simulator lane (Xcode 26.5) and the
shipping release/TestFlight lanes all run Xcode 26+ (Swift >= 6.2), so
production and CI always take this path. Older local toolchains keep a
`Thread.isMainThread`-guarded `assumeIsolated` fallback, so a stray
off-main release degrades to a best-effort skip instead of trapping
(outstanding work is `[weak self]` and `remoteClient` tears down via its
own deinit, so skipping never leaks). Shared teardown is extracted into
`tearDownOnDeinit()`.

Addresses autoreview P1 and the cubic-dev-ai thread at
MobileShellComposite.swift:952. Refreshes the Swift file-length budget
for the added lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The event-driven `waitForLineCount` awaited a continuation with no
timeout, so a stream that never reaches the expected chunk count would
hang the test instead of failing (the polling it replaced had a bounded
fallthrough). Add a `timeoutNanoseconds` parameter and throw
`LineCountWaitError.timedOut` with a diagnostic when the output never
arrives. The pending waiter resumes exactly once via a small
main-actor-isolated box, so the normal-completion and timeout paths can
never double-resume the continuation.

Addresses the cubic-dev-ai thread at cmuxFeatureTests.swift:43.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang and others added 3 commits July 2, 2026 01:57
…dergridterminalinputwaitsforliveeve

# Conflicts:
#	.github/swift-file-length-budget.tsv
…re-apply repair-replay fix)

origin/main (#7159/#7171) independently reworked the mobile render-grid
cold-attach replay subsystem: replay-lifecycle methods moved to
MobileShellComposite+TerminalReplayLifecycle.swift (widened to internal),
refreshTerminalEventSubscription became fire-and-forget, and new cold-attach
machinery was added (requestColdAttachTerminalReplay,
upgradePendingColdTerminalReplaysIfNeeded, full-replacement tracking).

Resolution:
- MobileShellComposite.swift: reset to origin/main's version, then re-applied
  this PR's exact net delta on top — the compiler-gated isolated deinit +
  tearDownOnDeinit(), the refreshTerminalEventSubscription repair mechanism
  (replaySurfaceIDsIfRepaired:), and replayAfterRepairedTerminalEventSubscription
  (which supersedes an in-flight replay before re-requesting so a post-repair
  catch-up is not coalesced behind a stale cold-attach snapshot). Verified the
  resolved file equals origin/main + this PR's net delta exactly, with no
  duplicate declarations against the relocated extension methods.
- MobileShellRenderGridLivenessTestSupport.swift: unioned both sides' mock
  replay mechanisms (per-surface replayFramesBySurfaceID for the liveness tests;
  replayRenderGridFrames queue for origin/main's cold-attach/staleness tests).
- .github/swift-file-length-budget.tsv: regenerated via
  scripts/swift_file_length_budget.py --write-budget.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The pre-6.2 fallback deinit skipped teardown entirely on an off-main
final release (the `Thread.isMainThread` guard failed), leaking the
retained terminal event listener task (which holds `client` strongly
and parks in `for await`) and the mobile event subscription/socket.
Dropping task handles at dealloc does not cancel unstructured tasks.

origin/main already fixed deinit safety with an ungated `isolated
deinit` (guaranteed main-actor teardown, no trap, no skip), which the
CI/shipping toolchains (Swift 6.2+) all use. Revert to that exact form
so the deinit has zero delta from mainline; this PR's contribution is
the render-grid replay-repair supersede, which is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dergridterminalinputwaitsforliveeve

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
#	Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellRenderGridLivenessTestSupport.swift
…dergridterminalinputwaitsforliveeve

# Conflicts:
#	.github/swift-file-length-budget.tsv
@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — b59b9248 Deployed Jul 5, 2026 by vercel[bot]
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.

iOS CI: renderGridTerminalInputWaitsForLiveEventBeforeReplay flakes on the iPad simulator leg, masked by the test-ios false-green override

3 participants