Skip to content

fix(ios): avoid transient duplicate final replies - #98117

Merged
joshavant merged 3 commits into
openclaw:mainfrom
ooiuuii:fix/ios-chat-final-session-dedupe
Jul 1, 2026
Merged

joshavant merged 3 commits into
openclaw:mainfrom
ooiuuii:fix/ios-chat-final-session-dedupe

Conversation

@ooiuuii

@ooiuuii ooiuuii commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #98116

What Problem This Solves

Fixes an issue where users viewing an iOS Chat conversation could briefly see the same assistant final reply twice when the live final event and canonical transcript event both arrived for the same turn. The later history refresh eventually reconciled the transcript back to one row, but the intermediate UI looked like OpenClaw duplicated and then deleted a final answer.

Why This Change Was Made

The shared chat view model now treats assistant rows inserted from live chat.final events as provisional for the current turn. A matching canonical session.message adopts that provisional row instead of appending a duplicate, and a later live final is skipped when the canonical assistant row is already visible after the latest user message. The match is intentionally scoped to messages after the latest user turn so an older provisional OK/Done response cannot consume a later identical reply.

This does not globally dedupe assistant messages by text, and it does not change the Gateway, Tailscale/Funnel, or pairing behavior.

User Impact

Users on iOS remote-domain or LAN Chat surfaces should see one stable final assistant row per turn while live final, session transcript, and history refresh events reconcile. Repeated identical answers on separate turns remain visible as separate replies.

Evidence

AI-assisted: yes, Codex.

  • Linked issue: [Bug]: iOS Chat briefly duplicates final assistant reply before history reconciliation #98116 describes the real iOS remote-domain symptom, environment, and source-level reproduction path.
  • Added shared OpenClawKit regression coverage:
    • session message adopts provisional final event reply covers live chat.final first, canonical session.message second, including a live content id that the canonical transcript does not carry.
    • final event does not duplicate canonical assistant session message covers canonical session.message first, later matching live chat.final second.
    • later identical session reply does not adopt prior turn provisional final covers the important repeated short-answer case so a stale provisional OK from a previous user turn cannot steal the next turn's canonical OK reply.
  • Diff sanity: git diff --check HEAD~1 HEAD passed.
  • Tooling limitation: this Windows host does not have Swift installed (swift --version is not recognized), so the Swift tests could not be executed locally here. They are added for the macOS/iOS CI lane to run.
  • Auto review: first local autoreview found a valid P2 stale-provisional-turn issue; that finding was accepted and fixed by scoping provisional adoption to messages after the latest user turn and adding the repeated OK regression test. A second autoreview rerun timed out after 6 minutes without a final verdict, so this PR is not claimed as autoreview-clean.

@clawsweeper

clawsweeper Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed June 30, 2026, 11:32 PM ET / 03:32 UTC.

Summary
The PR adds shared OpenClawKit final-message reconciliation and Swift regressions so matching live chat.final and canonical session.message assistant rows adopt/suppress one visible row instead of briefly rendering duplicates.

PR surface: Other +404. Total +404 across 2 files.

Reproducibility: yes. Source inspection shows current main can append a live chat.final assistant row and a canonical session.message assistant row with timestamp/content-metadata-sensitive dedupe, and the PR comment adds a live baseline with duplicate marker count 2.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #98116
Summary: This PR is the open implementation candidate for the canonical iOS Chat duplicate-final-row bug report.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🐚 platinum hermit
Proof: 🦞 diamond lobster
Patch quality: 🐚 platinum hermit
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • none.

Risk before merge

  • [P1] The patch intentionally changes shared OpenClawKit assistant-row adoption/suppression, so maintainers should treat it as a native chat message-delivery behavior change even though the iOS symptom is narrow.
  • [P1] The reconciliation key ignores timestamp and content id for final-row matching within the scoped user turn; the tests cover repeated identical replies across turns, but maintainers still need to accept that same-turn matching is the desired product behavior.

Maintainer options:

  1. Land with current proof (recommended)
    Maintainers can accept the bounded shared-native message reconciliation change because the branch now has live before/after output, focused Swift regressions, and green relevant checks.
  2. Ask for extra multi-client proof
    If maintainers are concerned about non-local run ordering, request one additional native chat proof covering another client's final/session-message sequence before merge.

Next step before merge

  • [P2] No separate repair job is needed because this PR is already the linked implementation candidate and I found no narrow ClawSweeper repair finding.

Security
Cleared: The diff only changes shared Swift chat UI reconciliation and Swift tests; it does not touch secrets, workflows, dependencies, package metadata, install scripts, or other supply-chain surfaces.

Review details

Best possible solution:

Land this shared OpenClawKit reconciliation after maintainer review accepts the message-delivery tradeoff and the current green checks remain valid.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main can append a live chat.final assistant row and a canonical session.message assistant row with timestamp/content-metadata-sensitive dedupe, and the PR comment adds a live baseline with duplicate marker count 2.

Is this the best way to solve the issue?

Yes. The duplicate is created where the live chat and canonical session streams converge in OpenClawChatViewModel, so run/user-turn scoped provisional-final adoption is narrower than changing Gateway transport or globally deduping by assistant text.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against fa3c9de45965.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The updated PR comment provides after-fix live output from real Gateway plus mock OpenAI plus iPhone 17 simulator showing duplicate marker count dropping from 2 on current main to 1 on the branch.
  • add rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The updated PR comment provides after-fix live output from real Gateway plus mock OpenAI plus iPhone 17 simulator showing duplicate marker count dropping from 2 on current main to 1 on the branch.
  • remove rating: 🧂 unranked krab: Current PR rating is rating: 🐚 platinum hermit, so this older rating label is no longer current.
  • remove status: 📣 needs proof: Current PR status label is status: 👀 ready for maintainer look.

Label justifications:

  • P2: This is a normal-priority user-visible iOS Chat duplicate-message bug with a bounded shared view-model fix and limited blast radius.
  • merge-risk: 🚨 message-delivery: The diff changes when assistant final rows are adopted or suppressed in shared native chat rendering, which can affect visible message delivery semantics.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The updated PR comment provides after-fix live output from real Gateway plus mock OpenAI plus iPhone 17 simulator showing duplicate marker count dropping from 2 on current main to 1 on the branch.
  • proof: sufficient: Contributor real behavior proof is sufficient. The updated PR comment provides after-fix live output from real Gateway plus mock OpenAI plus iPhone 17 simulator showing duplicate marker count dropping from 2 on current main to 1 on the branch.
Evidence reviewed

PR surface:

Other +404. Total +404 across 2 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 2 410 6 +404
Total 2 410 6 +404

What I checked:

Likely related people:

  • vincentkoc: Git blame and GitHub commit metadata show the current shared native ChatViewModel, tests, and iOS gateway chat transport were introduced into current main by this account in commit cb00045. (role: current main implementation introducer; confidence: high; commits: cb00045afca9; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift, apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift, apps/ios/Sources/Chat/IOSGatewayChatTransport.swift)
  • mbelinky: Targeted history found prior iOS ChatUI stability, canonical main-session alias, and reconnect-gap work touching the same shared native chat area. (role: feature-history contributor; confidence: medium; commits: 6effcdb551a8, fe3f0759b5c4, 42d11a3ec5f4; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift, apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift, apps/ios/Sources/Chat/IOSGatewayChatTransport.swift)
  • joshavant: This account pushed the final head rewrite and supplied before/after live Gateway plus iPhone simulator proof for the exact reconciliation behavior under review. (role: current PR rewrite and proof contributor; confidence: medium; commits: 71be30733238; files: apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift, apps/shared/OpenClawKit/Tests/OpenClawKitTests/ChatViewModelTests.swift)
  • Solvely-Colin: Related merged iOS Pro UI and gateway-flow work wired the iOS Chat surface through the shared OpenClawKit chat model and gateway transport implicated here. (role: adjacent iOS chat surface contributor; confidence: medium; commits: f6e51ff99af4; files: apps/ios/Sources/Design/ChatProTab.swift, apps/ios/Sources/Chat/IOSGatewayChatTransport.swift, apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal backlog priority with limited blast radius. merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. labels Jun 30, 2026
@joshavant
joshavant force-pushed the fix/ios-chat-final-session-dedupe branch from 6464227 to 71be307 Compare July 1, 2026 03:19
@joshavant

Copy link
Copy Markdown
Collaborator

I pushed a production-grade rewrite of the iOS final-message reconciliation in 71be307332.

Fix shape:

  • Scope provisional final chat-event rows to the run's user-turn boundary instead of deduping by assistant text within the current visible tail.
  • Reconcile durable session.message rows onto matching provisional final rows only when they belong to the same user turn.
  • Avoid suppressing or adopting a later identical assistant reply in the same session.

Evidence:

  • Current-main live repro baseline: ISSUE_98116_MAX_MARKER_ASSISTANT_COUNT=2, duplicate final marker texts [ "OPENCLAW_98116_E2E_OK", "OPENCLAW_98116_E2E_OK" ].
  • Branch live fix proof on real Gateway + mock OpenAI + iPhone 17 simulator using GatewayNodeSession -> IOSGatewayChatTransport -> OpenClawChatViewModel: ISSUE_98116_MAX_MARKER_ASSISTANT_COUNT=1, final marker texts ["OPENCLAW_98116_E2E_OK"], test succeeded.
  • Focused Swift regressions passed: session message adopts provisional final event reply, final event does not duplicate canonical assistant session message, later identical session reply does not adopt prior turn provisional final, and dedupes gateway echo of local user message.
  • swiftformat --lint passed for apps/shared/OpenClawKit/Sources/OpenClawChatUI/ChatViewModel.swift.
  • .agents/skills/autoreview/scripts/autoreview --mode local ended clean with no accepted/actionable findings.

@joshavant

Copy link
Copy Markdown
Collaborator

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🦞🧹
ClawSweeper re-review requested.

I asked ClawSweeper to review this item again.
Action: item re-review queued (workflow sweep.yml, event repository_dispatch).
Result: the existing ClawSweeper review comment will be edited in place when the review finishes.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Jul 1, 2026
@joshavant
joshavant merged commit 5a5913a into openclaw:main Jul 1, 2026
41 checks passed
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 1, 2026
* Fix iOS final reply dedupe

* fix(ios): scope final message reconciliation

* docs(ios): explain final message reconciliation key

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
chenyangjun-xy pushed a commit to chenyangjun-xy/openclaw that referenced this pull request Jul 1, 2026
* Fix iOS final reply dedupe

* fix(ios): scope final message reconciliation

* docs(ios): explain final message reconciliation key

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 8, 2026
* Fix iOS final reply dedupe

* fix(ios): scope final message reconciliation

* docs(ios): explain final message reconciliation key

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
(cherry picked from commit 5a5913a)
Rorqualx pushed a commit to Rorqualx/cortex that referenced this pull request Jul 15, 2026
* Fix iOS final reply dedupe

* fix(ios): scope final message reconciliation

* docs(ios): explain final message reconciliation key

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
(cherry picked from commit 5a5913a)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-risk: 🚨 message-delivery 🚨 May drop, duplicate, misroute, suppress, or wrongly target messages. P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. size: M status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: iOS Chat briefly duplicates final assistant reply before history reconciliation

2 participants