Repository navigation
fix(ios): replace failed agent turns on daemon retry - #27314
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
|
Found 1 test failure on Blacksmith runners: Failure
|
|
Bead |
Fleet receipts for #27314Current head 2336210Fleet iOS compile + full CmuxMobileKit simulator tests at 2336210 ( Red/green: not rerun at this head. The latest red/green is at 391bc06 (below): red d4288e3 fails Screenshots (Demo build, Agent chat,
Failed turn with Retry,
No demo scene shows a failed turn, and the mock session answers The two look the same. Base already showed a Retry button on a failed turn (it re-sent the prompt). The head shows the same card, but its Retry now goes through Capture fixture (MockAgentSession.swift, same hunks at base and head)--- a/cmux-next-ios/CmuxMobileKit/Sources/CmuxMobileAgentChat/MockAgentSession.swift 2026-10-29 02:52:38
+++ b/cmux-next-ios/CmuxMobileKit/Sources/CmuxMobileAgentChat/MockAgentSession.swift 2026-10-29 02:52:38
@@ -52,7 +52,7 @@
// History (and a waiting turn's opening) arrive as one change.
batch = []
replayHistory()
- if summary.status == .waiting { startWaitingTurn() }
+ if summary.status == .waiting, !Self.failedTurnDemo { startWaitingTurn() }
let replayed = batch ?? []
batch = nil
continuation.yield(.batch(replayed))
@@ -164,6 +164,13 @@
.init(value: "high", name: "High"), .init(value: "max", name: "Max"),
])]
}
+
+ #if DEBUG
+ /// Capture fixture only: `-cmux-mobile-failed-turn-demo YES` ends the history with one failed turn.
+ static var failedTurnDemo: Bool { UserDefaults.standard.bool(forKey: "cmux-mobile-failed-turn-demo") }
+ #else
+ static let failedTurnDemo = false
+ #endif
private func replayHistory() {
emit(.modelsAvailable(Self.models, current: currentModel))
@@ -193,6 +200,13 @@
emit(.turnResult(turnId: turnID, status: .completed, errorText: nil, at: start.addingTimeInterval(41)))
let second = Date().addingTimeInterval(-1800)
replaySubagentTurn(at: second)
+ if Self.failedTurnDemo {
+ let failedAt = Date().addingTimeInterval(-600)
+ emit(.userMessage(text: "Run the link tests again", promptId: "p-h3", turnId: "history-3", attachments: [], at: failedAt))
+ emit(.turnStarted(turnId: "history-3", at: failedAt))
+ emit(.turnResult(turnId: "history-3", status: .failed, errorText: "The agent process exited unexpectedly.",
+ at: failedAt.addingTimeInterval(12)))
+ }
checkpointLog = [
AgentCheckpoint(id: "cp-h1", title: "What does the link framing look like today?", turnID: turnID, createdAt: start,
branch: summary.branch, head: "4d81e0c", files: 0),Earlier head 391bc06 (superseded)Red/green (macOS
Fleet iOS compile + full CmuxMobileKit simulator tests at 391bc06 (step Screenshots, agent chat (
The Earlier head 51c54b8 (superseded; kept for the record)Red/green (macOS
Fleet iOS compile + full CmuxMobileKit simulator tests at 51c54b8 (step Screenshots, agent chat (
Both Demo builds succeeded (exit 0). The Agent session: agent session |
|
Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 51c54b8 BLOCK
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
|
Review (codex-review-box/gpt-6-astra high, lane-place): BLOCK at 391bc06 BLOCK
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
Apply existing phone mode and Full-access checks to retry. Keep retained attempt output separate and disable Retry when its turn is no longer eligible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
|
Rust red/green: Both commits ran the same command on Linux:
Red failure: Each snapshot has the same tree as the commit it tested: Still running on the fleet: the iOS simulator compile plus the CmuxMobileKit tests, including Agent session: agent session |
|
Fleet receipt: head Commands (fleet Macs, Xcode 26.6, iPhone 17 Pro on iOS 26.5): Head
Red
The Rust Fleet steps: head Agent session: agent session |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-2209fa76608f5c2d6757
|
Merged current base 719c550 into this branch. New head: 09f4248, confirmed on the remote. The two UI conflicts preserve the landed Retry with another model controls alongside the failed-turn Retry action and eligibility check. The earlier SHIP and fleet receipt 1236ed65 cover 2336210. This new head needs a fresh independent review and fleet evidence:
Dispatch is currently blocked: the message relay cannot read the destination session list, so the direct fleet requests were not delivered. No new test or review success is claimed. |
|
Landing receipt at
Agent session: agent session |
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
|
|
Shipped in:
|
Base's new iOS work carried onto #27087's lists: - Failed turn retry through acpmux (#27314): the native transcript's failure row retries the turn with store.retryFailedTurn (disabled while a retry is in flight or once the turn is not the latest failed one), as base's TurnView did. The coalesced chunk path keeps base's startsNewAttempt, so a retried attempt's first chunk starts a new message or thought. - Top chrome (#27300): the transcript's collection view takes the native soft top edge and the edge inset base gave its scroll view (edgeTopInset moves onto ChatTranscriptCollectionView), with the top shelf and the iOS 26 gradient fallback for workspace chats. - Open on iPhone (16bfeba): a native browser tab's card shows its own page first, then the streamed one, then a local page only if one was shown. - Base's SwiftUI TurnView stays removed (the native rows replace it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Agent-Session: https://cmux.com/deeplink/agent?id=aid-7aa94f16aeac121e03bf
…lists 13 files conflicted under cmux-next-ios/CmuxMobileKit/Sources. Both sides' behavior is kept. Where this branch replaced the code an upstream commit changed, the upstream change is ported into the new code: - Transcript (TurnView/AgentChatTurns -> ChatTranscriptList rows): Find in Chat (#27318) marks and scrolls to the row that draws the match (ChatRow.findKey, layout frames, the marked row drawn as SwiftUI); Edit and Resend opens in the composer with the "Replace your draft?" dialog on both row paths (#27620); "Showing messages since" for capped history in the SwiftUI and native session line (#27374); the failure card retries through retryFailedTurn with Retry off while it is in flight (#27314); Retry keeps attachments (#28489) and Retry with another model in the SwiftUI and native More menu (#27322); native copy haptic follows the haptics setting (#27348). - AgentChatStore: the branch's copy-free chunk append keeps upstream's startsNewAttempt. - ChatKeyboardDock: upstream's header bottom, native top edge, iOS 26 fade, stack ceiling and find scroll (#27300, #27767, #27318) on the branch's collection view; the edge-inset safe area override moves to ChatTranscriptCollectionView. - DiffFileScreen (VStacks -> DiffCodeList): Search in File, Next / Previous Hunk, match paint, current-match bar and scroll to line (#27322), memoized per file and query. - WorkspacesList (SwiftUI menus -> WorkspacesListMenus): group Options, Close All in Group, Close Other Groups, Remove All Workspace Colors, Move to Top/Bottom, Sort Workspaces, Close Other in Group / All (#27322). - WorkspaceSync: upstream's delta queue (#27299) kept; the repeated-title fix moves to enqueue. TerminalFeed: upstream's delta early return before the batched reads. - FeedStore: markAllRead next to batched markRead. ArtifactPreview: cancellation check and off-main decode. DevicesPage: link status in the subtitle with the per-pass row context. WorkspaceTabsSource: Open on iPhone cards and pages, group edits and screens on the stored-tab model; MarkdownView: block marks; DisclosureChoice: both. - Tests: SlashSuggestionsReachTests uses the controller's store initializer; TranscriptRowPortTests covers the ported transcript and diff behavior. Per-commit table: cmux-next-ios/verify/listperf/MERGE-<this sha>.md (next commit). Tests: macOS swift test (aws-m4pro-5) 1500 tests, the same 4 PreviewInsufficientSpace failures as untouched upstream on that host; iOS xcodebuild test (aws-m4pro-6, iOS 26.5) all suites pass but BoundedWaitTests under load, which passes on re-run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
![Fix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/fix-with-codesmith-light-3x.png)
















Failed agent Retry previously sent a fresh user prompt, duplicating the failed turn. It now calls the daemon's retry operation. The daemon records the previous turn id on each retry attempt; the phone folds that attempt into the failed turn on live delivery and history replay, retaining the original prompt, attachments and partial work. A refused request leaves the failed turn intact, and repeated taps are suppressed while the request is pending.
Leo's request:
Scope: ordinary Retry on a failed latest agent turn. Chief attachments are covered separately by #27286. Edit and Resend still needs the historical-fork path and navigation support. #27205's menu additions are preserved; its failed-turn model retry should use the daemon retry operation after the model change.
Validation:
Risk: an older daemon without the retry operation refuses the request and keeps the failed turn. No fallback sends a duplicate prompt. No timers or observers are added. Shared CmuxConversation and CmuxTabsKit are unchanged.
Agent session: agent session
🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes iOS retry of a failed agent turn so it uses the daemon's retry operation instead of sending a duplicate prompt, preserving the original prompt, attachments, and partial work.
Written for commit 09f4248. Summary will update on new commits.