Repository navigation
fix(mobile-chat): every phone send is its own message - #23917
Draft
brennanb2025 wants to merge 9 commits into
Draft
brennanb2025 wants to merge 9 commits into
brennanb2025 wants to merge 9 commits into
Conversation
7 of 9 tasks
brennanb2025
force-pushed
the
brennanb2025/chat-op-id-per-action
branch
from
September 29, 2026 22:11
de73921 to
bd08f9e
Compare
brennanb2025
force-pushed
the
brennanb2025/mobile-send-identity-per-message
branch
from
September 29, 2026 22:11
005f27b to
8e77ca3
Compare
brennanb2025
force-pushed
the
brennanb2025/chat-op-id-per-action
branch
from
September 29, 2026 22:26
bd08f9e to
154cc72
Compare
brennanb2025
force-pushed
the
brennanb2025/mobile-send-identity-per-message
branch
3 times, most recently
from
September 29, 2026 22:34
2ff476e to
89aca87
Compare
A client kept an operation id per method and payload across presses, so a later identical press replayed an earlier action instead of running: an option picked again stayed on the other one, a goal set again stayed cleared, and a second Stop or phone Stop did nothing. Every press now mints its own id, on desktop and phone, and no client keeps a retry id. The host answers a harmless repeat from what the chat records: the same prompt answer again returns the resolution it holds, and a Cancel of a prompt already cancelled answers ok. A second Stop of the same turn while the first is on its way joins it.
…th that clear Every press now carries its own operation id, so a /clear that reaches the host after this caller's /clear already committed (a double press, or a retype after a lost answer) no longer replays the first id. It started the cleared conversation's agent and then refused it with "This conversation has been cleared." The host now answers such a /clear from the committed record: the same caller, on a conversation whose tab moved to its replacement, gets that clear's result, replacement included, before admission starts anything. No second clear runs. Another window, or a cleared conversation reopened from history, still reads "cleared".
…e gates that ran it
brennanb2025
force-pushed
the
brennanb2025/chat-op-id-per-action
branch
from
September 29, 2026 22:51
09fa9e9 to
89564af
Compare
The phone keyed a structured send by its text in a durable journal, so sending the same text again while the first was unconfirmed silently joined the first, the same text was refused for good once its entry aged past the host's admission window, and about 47 unsettled entries outgrew the page store so every later send failed. Each press now mints its own operation id, which the host also keeps as the message's client id. New sends never read or write the journal, so a storage failure no longer blocks a send. The v1 key stays readable for older host-served pages; reconciliation keeps clearing entries the host shows settled and now also prunes entries past the host's replay window, removing the key once it is empty.
…xt-keyed send read
…the text-keyed send needed
…pped content fingerprint Cause: phone sends no longer key by message text, so the image content fingerprint that only fed that key is gone from each uploaded attachment. The three native-chat-image-upload goldens lose exactly that field (a multiset compare of each shows no other added or removed value); every other golden moves only its baseline header, repinned to 764fc30, the last commit that touches a recorded path.
brennanb2025
force-pushed
the
brennanb2025/mobile-send-identity-per-message
branch
from
September 29, 2026 22:55
89aca87 to
b84fbde
Compare
brennanb2025
force-pushed
the
brennanb2025/chat-op-id-per-action
branch
from
September 30, 2026 08:18
89564af to
7801b8f
Compare
This was referenced Oct 1, 2026
Closed
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
The phone app identified a chat message by its text. If you sent "yes", the reply didn't confirm, and you sent "yes" again, the second one was quietly merged into the first and never sent. The same text could also be refused forever after a day, and after about 47 unconfirmed messages every send failed. Now every tap of Send is its own message.
The plan this belongs to
Goal: one operation id per user action for every structured chat write. It ships as:
What Changed
The problem. The phone app gave a structured chat message the id of any earlier, still-unconfirmed message with the same text and attachments, looked up in a saved list on the phone keyed by that text. The list existed so that retyping a message would retry it, because the phone keeps no saved copy of the message and has no Retry button. It caused:
Scope. Structured Claude and Codex chats in the phone app, on any host. Desktop sends already used their own id per message and are unchanged.
Before / After
Mechanism
mobile-structured-agent-session-send.tsmints the message id once per press; it is also the operation id, as the host already expects for a send.orca:mobileStructuredSendOperations:v1stays readable, because pages served by older hosts still write it. Its cleanup now also drops entries older than the host's replay window, besides entries the host shows as settled, and removes the key once empty. Old entries cannot be moved onto their messages, because they store only hashes of the text.Why
A message is a user action, and two messages with the same words are still two messages. Keeping an id per text is what let one swallow the other and let entries pile up forever. Reusing an id is only right for a retry of that one message, and the phone has no stored message a retry could come from, so nothing is kept. The desktop and one of the reference patterns behave the same way: a retyped message is a new message.
Alternatives considered:
Differences from the common pattern
Linked Issue
No issue. Step 2 of the plan above.
Visual Proof
QA_PLACEHOLDER
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
use-mobile-structured-agent-session-send.test.tsx: the same text after a lost reply, an error or an unknown answer gets two ids and the second is accepted; a remount mints a new id and writes nothing to storage; a re-uploaded image is a new message; a v1 entry for the same text is not joined and stays untouched; entries past the replay window are pruned; a settled v1 entry is cleared; a store that rejects reads and writes still sends.The saved-list unit tests and the page storage tests are rewritten for the cleanup-only journal.
9 of the 13 send-hook tests fail on the base; removing each rule fails its test.
pnpm tc:node,tc:web,tc:cli, the mobile typecheck and test ratchet, oxlint,check:code-quality:changed,check:react-doctor:changedand the anti-slop audit pass.AI Disclosure
Review
REVIEW_PLACEHOLDER
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Merge order: #23524, then #23935, then #23916, then #23917.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)Author: @BrennanKB5