fix(ios): keep offline chat sends on their original route#100942
Conversation
|
Land-ready proof for head
Known gap: the full terminate/relaunch live scenario preceded the final main rebase; the final head was revalidated by the 181-test simulator slice and exact-head hosted CI. |
|
Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 10:58 AM ET / 14:58 UTC. Summary PR surface: Source +79, Tests +99, Docs 0, Other +4709. Total +4887 across 42 files. Reproducibility: yes. from source: current main's merged outbox lacks route-bound confirmation, while the PR binds replay to captured gateway/session routing and canonical-history confirmation. I did not run a live iOS simulator in this read-only review, so the reproduction is source-based plus contributor live proof. Review metrics: 2 noteworthy metrics.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review detailsBest possible solution: Land the route-bound outbox after maintainers accept the fail-closed legacy outbox upgrade behavior and the exact-head CI/live proof as sufficient for the Apple chat delivery path. Do we have a high-confidence way to reproduce the issue? Yes from source: current main's merged outbox lacks route-bound confirmation, while the PR binds replay to captured gateway/session routing and canonical-history confirmation. I did not run a live iOS simulator in this read-only review, so the reproduction is source-based plus contributor live proof. Is this the best way to solve the issue? Yes, this appears to be the best owner-boundary fix: route capture lives in the Apple transports, the guard is additive in the Gateway protocol, and durable state stays in the existing SQLite chat cache/outbox rather than adding a parallel store. The main alternative, automatic replay without a routing contract, would preserve the misroute and duplicate ambiguity the PR is fixing. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 83f052772429. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +79, Tests +99, Docs 0, Other +4709. Total +4887 across 42 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
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
|
Security-sensitive guard clearedThis PR no longer has blocked security-sensitive file changes. A future security-sensitive change requires a fresh
|
7214386 to
b6c6b8a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b6c6b8ae97
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if self.transport.outboxRequiresSessionRoutingContract, | ||
| next.routingContract != routeLease.sessionRoutingContract |
There was a problem hiding this comment.
Don't park exact agent-session outbox rows on default changes
When a queued row already has an exact agent-scoped deliverySessionKey such as agent:ops:ios-..., its target is not affected by later changes to the gateway default agent or main-session key. This unconditional routing-contract comparison still marks the row as delivery_target_changed whenever that global contract changes, so offline sends from explicit agent sessions get parked and require manual retry even though replaying them cannot retarget to the new default. Consider applying the contract mismatch gate only to mutable aliases like main/global, or persisting whether the row actually depends on the routing contract.
Useful? React with 👍 / 👎.
|
Merged via squash.
|
Related: #100331, #100196, #46664
What Problem This Solves
Fixes an issue where iOS users who queued chat text while offline could lose the draft, replay it to a newly selected gateway or agent, duplicate an ambiguously accepted send, or lose the queued turn after relaunch.
Why This Change Was Made
The follow-up replaces the landed single-view queue with one gateway-scoped SQLite outbox and one canonical delivery path. Enqueue captures the gateway, agent, canonical session key, thinking level, and routing contract; replay leases that exact route, fails closed on route changes or ambiguous delivery, and removes rows only after canonical history confirms them. iOS and macOS share the same lifecycle, while live sends remain compatible with older gateways and queued replay requires the additive routing capability.
User Impact
Offline text survives termination and relaunch, stays visible in the correct transcript, and flushes once after the original gateway reconnects. Agent or gateway changes cannot silently retarget queued text. Definitive rejections remain retryable; uncertain delivery waits for reconciliation or explicit user action.
Evidence
outbox_commandsdrained to zero.swift test --package-path apps/shared/OpenClawKit --filter ChatTranscriptCacheStoreTests— 36 passed.swift test --package-path apps/shared/OpenClawKit --filter ChatViewModelOutboxTests— 45 passed.swift test --package-path apps/shared/OpenClawKit --filter ChatViewModelTests— 144 passed.IOSGatewayChatTransportTests,RootTabsSourceGuardTests,NodeAppModelInvokeTests) — 181 passed.node --import tsx scripts/native-app-i18n.ts check, SwiftFormat, oxfmt, andgit diff --checkpassed.