fix(ios): restore in-flight runs after reconnect#100277
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8b0cb66d9d
ℹ️ 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".
|
Codex review: needs changes before merge. Reviewed July 5, 2026, 9:59 PM ET / 01:59 UTC. Summary PR surface: Source +53, Tests +83, Other +1531. Total +1667 across 22 files. Reproducibility: yes. source inspection provides a high-confidence path: base Apple chat clears local pending state and lacks Review metrics: 1 noteworthy metric.
Stored data model Root-cause cluster Members:
Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything. 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:
Copy recommended automerge instructionNext step before merge
Security Review findings
Review detailsBest possible solution: Keep the existing Do we have a high-confidence way to reproduce the issue? Yes, source inspection provides a high-confidence path: base Apple chat clears local pending state and lacks Is this the best way to solve the issue? No, not yet complete. Using the existing Gateway Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 4a5bdc6ae3a9. Label changesLabel changes:
Label justifications:
Evidence reviewedPR surface: Source +53, Tests +83, Other +1531. Total +1667 across 22 files. View PR surface stats
Acceptance criteria:
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
Review history (16 earlier review cycles; latest 8 shown)
|
8b0cb66 to
4030223
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4030223d78
ℹ️ 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".
c9a5ce2 to
eb669c2
Compare
4030223 to
20fad5f
Compare
eb669c2 to
262de5b
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40fec95283
ℹ️ 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".
622c3b6 to
ee14abf
Compare
|
Land-ready rewrite at What changed:
Proof:
Live simulator proof on iPhone 17 / iOS 26.5: started a delayed tool-backed run, backgrounded the app, reopened while active, and observed exactly one user row plus restored Dependency contract checked directly against Codex Known proof gaps: none for the touched behavior after exact-head hosted CI completes successfully. |
ee14abf to
b4a5cfa
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7002c17c8d
ℹ️ 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".
| } | ||
|
|
||
| init(gateway: GatewayNodeSession) { | ||
| init(gateway: GatewayNodeSession, globalAgentId: String? = nil) { |
There was a problem hiding this comment.
Pass globalAgentId through Watch chat transports
Fresh evidence beyond the earlier selected-global path is that NodeAppModel's Watch preview/send paths still instantiate IOSGatewayChatTransport(gateway:) without an agent (apps/ios/Sources/Model/NodeAppModel.swift:4279 and :4683). When the iPhone is focused on literal global for a non-default agent, the Watch snapshot carries sessionKey: "global", but requestHistory/chat.send now include agentId only through selectedGlobalAgentId(for:) populated by this initializer; with the default nil, the gateway resolves global to the default agent (src/gateway/server-methods/chat.ts:3091-3100, src/gateway/session-create-service.ts:50-83). That makes Watch previews, sends, and reply readback hit the wrong transcript, so pass self.chatAgentId into those transports or route them through makeChatTransport.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd4d69d3f1
ℹ️ 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".
| } | ||
| const code = (details as { code?: unknown }).code; | ||
| return typeof code === "string" && code.trim().length > 0 ? code.trim() : null; | ||
| return typeof code === "string" && code.trim().length > 0 ? code : null; |
There was a problem hiding this comment.
Trim connect detail codes before comparing
When a gateway connect error arrives with padding in details.code, this now returns the raw string even though the helper only accepts it based on code.trim().length. Callers compare the returned value to exact constants (PAIRING_REQUIRED, PROTOCOL_MISMATCH, AUTH_TOKEN_MISMATCH, etc.), so padded structured errors fall through to generic formatting and can skip recovery paths such as device-token retry/cleanup in packages/gateway-client/src/client.ts. Return the trimmed code as before so untrusted but otherwise valid detail payloads still drive the intended reconnect behavior.
Useful? React with 👍 / 👎.
Restore active Apple chat ownership across reconnect, foreground, and sequence-gap recovery using the existing chat.history snapshot. Preserve agent/session scoping and Gateway user-turn identity across Codex and Copilot mirrors, including current offline-cache integration.
|
Landed as Exact-head CI run 28763343519 passed on |
Part of #100197. Supersedes the stacked
afterSeqdesign in #100267.What Problem This Solves
When iOS backgrounds, reconnects, changes sessions, or misses an event sequence, it can lose ownership of a run that is still active on the Gateway. The transcript may look idle, Abort disappears, and a second send becomes possible while the first run is still working.
Why This Change Was Made
The Gateway already exposes the canonical active-run snapshot through
chat.history.inFlightRun; the TUI already consumes that contract. This rewrite removes every proposedafterSeqprotocol/client change and restores ownership from the existing snapshot instead.The shared Apple chat model now restores run ID, buffered text, tool state, Abort, and the send lock during bootstrap, foreground resume, reconnect, sequence-gap recovery, post-send retries, and completion refreshes. History and live events use independent request/generation ordering, global sessions remain agent-scoped, current iOS offline-cache prepaint/write-through remains intact, and changing the active agent clears all session-owned pending state before rebootstrap.
Codex and Copilot transcript mirrors preserve the Gateway-owned
<runId>:useridempotency key while retaining separate provider mirror metadata. This gives optimistic UI, Gateway history, and provider transcripts one stable user-turn boundary.Direct Codex app-server source confirms a turn becomes active before message deltas, so an active run with an empty buffered string is valid ownership.
User Impact
Returning to iOS or macOS chat no longer loses an active response or permits an overlapping send. Recovery works without a new Gateway protocol/cursor, and older Gateways retain the previous transcript-based completion fallback.
Evidence
31cf8b9416c10797dce7e3055cad634add04c0d4, including iOS build, macOS Swift/node, native i18n, Android, Node shards, and QA smoke.git diff --checkpassed.