You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(mobile): a new thread's first message no longer returns to the composer after a lost launch reply - #32330
On mobile, the first message of a new thread could come back into the thread's composer after a network drop, even though the server had already received it. If the launch reply was lost, the outbox kept the queued task. Once the shell listed the new thread, the drain treated the task as edited after delivery and merged its text into the composer, so tapping Send again would duplicate it. The issue includes a server trace from a real device.
Change
The thread outbox delivery service now records the revision and workspace of each new task's launch, in memory only. If the shell lists the thread but the queued task is unchanged since that launch, the drain resends the launch instead of handing the text back. The resend reuses the original command id, message id and workspace. The server's launch is idempotent by command id: it replays a completed launch from its receipt, and dispatches the initial message only when that message's own receipt is missing. So a resend either confirms the launch or finishes one that stopped between creating the thread and accepting the message. The normal success path then clears the outbox without touching the composer.
A resend goes through the same gates as the first launch (live shell, connection, operation access). It reuses the launched worktree instead of preparing the pull request checkout again. A task edited after its launch, or one launched before an app restart (no in-memory record), is still handed back to the composer as before.
Focused tests in apps/mobile/src/state/threadOutboxDelivery.permissions.test.ts, describe "queued task whose launch reply is lost". They drive the real drain: the launch returns Socket is not connected after the server created the thread, the shell then lists the thread, and the retry timer fires.
resends an unedited launch instead of handing its text back: fails on main with expected 'Keep this draft separate\nMy dictated…' to be 'Keep this draft separate', the reported symptom. Passes here: one resend with the same command and message ids, composer untouched, outbox empty.
hands back a task edited while its launch was in flight: passes on main and here. It pins the intended hand-back.
holds the resend while operation access is missing: no dispatch while access is missing, then one resend once it returns.
resends a pull request task in its launched worktree without checking out again: one checkout, and the resend reuses the same bootstrap.
threadOutboxDelivery.permissions.test.ts and threadOutboxDelivery.test.ts pass, 45/45. vp run check:changed passes on the final head: format, lint, knip, typecheck, tests and react compiler.
Runtime evidence against a real server
The bug needs the server to commit a launch and the client to lose its reply. A driver runs each side's real mobile delivery service, outbox and composer code against that side's own dev server (fresh database, synthetic data) through a proxy that forwards the launch, holds the successful reply, then drops it and closes the socket. The client reconnects and receives the thread in the server's shell snapshot. Before = e5b0718a8a (main), after = 5551099eb1 (this fix; later commits only merge main). Environment: Linux x86_64, Node 26, non-UI runtime; no render code changed.
Row
Before
After
R1: lost reply, empty composer
transcript: no resend, My dictated idea returns to the composer
transcript: resend with the same command and message ids, composer stays empty
R2: lost reply, unrelated draft
transcript: sent text appended to Keep this draft separate
On every run the server holds exactly one user message and one turn, and the persisted outbox ends empty. What else changed: generated ids, timestamps and ports only. Full report with proxy logs, server counts and step timestamps: evidence.md. Scripts: run-evidence.mjs, runtime.template.ts, drop-proxy.mjs, query-counts.mjs. Fixture matrix: matrix.md; the other rows are covered by the tests above or unchanged.
Not checked on a device: the pooled emulator was unavailable, so native rendering and the connection supervisor were not exercised; the transcripts replace them with adapters that keep the delivery decision, composer merge and outbox removal real. Not changed: a launch whose reply was lost, followed by an app restart before the retry, still hands the text back. Without in-memory state, the client cannot tell that payload from an edit.
Verification log
Review round 1: two independent reviews found no code defects. One asked for runtime evidence, added above.
Proof audit: pass 1 derived the matrix and asked for R1/R2 against a real server; pass 2 checked every transcript against its logs, rebuilt R2-after independently with a matching outcome, and found the evidence sufficient.
Tests: 45/45 focused mobile tests; server launch-replay tests pass.
AI-assisted; verified with the tests and tools listed above.
…he composer
A queued new task whose launch reply is lost is retried; once the created
thread appears, the drain hands the already-delivered, unedited text back
to the composer. These tests reproduce #32313 and pin that an edit made
during the lost launch is still recovered.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…eply is lost
When the launch of a queued new task reached the server but its reply was
lost, the next drain found the created thread and always handed the queued
text back to the composer, inviting a duplicate send.
The delivery service now remembers the outbox revision each creation launch
carried in this session. If the queued task is unchanged since launch, the
drain finishes it like a delivered launch (revision- and editor-guarded
removal). Edits made after launch, or launches from before an app restart,
still go back to the composer.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…without it
Launch commits thread.create before the initial message, so a reply lost between
the two leaves an empty thread. Only finish the delivery once the shell shows a
user message.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…delivery
Launch commits the thread before its first message, so a thread in the shell does
not prove the message landed. Resending the unedited launch with its command id
lets the server replay it or finish the message, and drops the shell guard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
…s worktree
A resend entered through the remove action, skipping the live, connection and
operate-scope gates, so a session without operate scope redispatched without
backoff. It also re-ran the pull request checkout under the running agent.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Macroscope's review found this PR approvable — Mobile now retries an unchanged new-thread launch in its original workspace after a lost reply, rather than returning its first message to the composer. Existing delivery safeguards and command-ID replay keep the change limited to recovery behavior.
No code changes detected at 1c0c9c2. Prior analysis still applies.
You can add or adjust custom eligibility rules. Learn more.
Reviewing files that changed from the base of the PR and between d7c3070 and 241fbf8.
You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.
Use the checkbox below for a quick retry:
Trigger review
No actionable comments were generated in the recent review.
Walkthrough
The mobile thread outbox now tracks creation launch revisions and workspaces. Matching retries reuse the launched workspace and can resend the creation despite an existing thread. Tests cover edited messages, unavailable operation access, and pull-request retries.
Changes: Mobile creation retry recovery
Layer / File(s)
Summary
Track launches and route matching retries apps/mobile/src/state/threadOutboxDelivery.ts
The outbox records a creation’s revision and selected workspace. When the revision matches on retry, delivery reuses the workspace and allows the send path despite an existing thread.
Clean up launch records and test retry cases apps/mobile/src/state/threadOutboxDelivery.ts, apps/mobile/src/state/threadOutboxDelivery.permissions.test.ts
Cached launch records are removed when messages leave the queue. Tests cover unchanged and edited creations, missing operation access, and pull-request retries using the launched worktree.
Unedited first messages can be resent after a lost launch reply without creating a duplicate thread or message. No actionable merge risk remains beyond normal checks.
Pre-merge checks: 4
Check name
Status
Explanation
Title check
The title names the fix and the scenario: a new thread's first message no longer returns to the composer after a lost launch reply. It matches the main change in the changeset, which records launch re…
Linked Issues check
Directly linked issue #32313 has one coding objective: an unedited first message of a new thread, already delivered after a lost launch reply, must not return to the thread composer, and the outbox en…
Out of Scope Changes check
All changed hunks connect to the linked objective. In threadOutboxDelivery.ts, the launchedCreations map records each launch's revision and workspace. The launch path reuses that workspace when the re…
Description check
The description covers the problem, change, scope, and verification. It links the related issue and reports focused test results, runtime evidence, and limitations. The agent attribution does not name…
Full details: Title check
Explanation
The title names the fix and the scenario: a new thread's first message no longer returns to the composer after a lost launch reply. It matches the main change in the changeset, which records launch revision and workspace and resends an unedited launch. It follows the conventional commit format from the template and is specific enough for a teammate scanning history. At 101 characters it runs long, but it stays on the primary change.
Full details: Linked Issues check
Explanation
Directly linked issue #32313 has one coding objective: an unedited first message of a new thread, already delivered after a lost launch reply, must not return to the thread composer, and the outbox entry must clear without a duplicate send. The whole-PR diff (merge base 4cb6945 to head 8e212e2) implements this in apps/mobile/src/state/threadOutboxDelivery.ts. It adds an in-memory launchedCreations map that stores each launch's revision and workspace. When the shell lists the thread and the queued task's revision is unchanged, resendLaunch is true. The drain then treats the thread as not yet existing and resends the launch with the same command and message ids. The new test 'resends an unedited launch instead of handing its text back' drives the real drain and checks one resend, an untouched composer, and an empty outbox. The test 'hands back a task edited while its launch was in flight' keeps the hand-back for edited tasks. The test 'holds the resend while operation access is missing' checks the gates. The test 'resends a pull request task in its launched worktree without checking out again' checks worktree reuse. The issue's suggested plain-removal option was not taken, but the issue lists it only as one option. Two limits remain. First, duplicate prevention depends on the server treating a repeated launchThread with the same command id as idempotent. The PR description states this. I could not confirm it in the repository, because the apps/server/src/orchestration listing returned no files. Second, the resend path for a launch that is lost across an app restart is not changed. The issue's root cause describes the restart path as intended hand-back behavior, so this does not fail the objective.
Full details: Out of Scope Changes check
Explanation
All changed hunks connect to the linked objective. In threadOutboxDelivery.ts, the launchedCreations map records each launch's revision and workspace. The launch path reuses that workspace when the revision matches, so a resend does not check out the pull request again. The cleanup loop removes records once their messages leave the queue. The resendLaunch flag keeps the thread-exists gate on resend. The live recheck uses the same flag. The recovery comment text is updated to describe the new hand-back condition. In threadOutboxDelivery.permissions.test.ts, the import of threadOutboxRetryDelayMs and the new describe block 'queued task whose launch reply is lost' test only the new resend, hand-back, access-gate, and worktree-reuse behavior. No unrelated files or render code changed, as stated in the PR description and the diff.
Full details: Description check
Explanation
The description covers the problem, change, scope, and verification. It links the related issue and reports focused test results, runtime evidence, and limitations. The agent attribution does not name a model, but this is a minor omission.
Finishing Touches
Generate docstrings
Commit to this branch
Create a new PR
Generate unit tests (beta)
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
Handing this investigation over to you. On corex Linux at base 4cb6945dc60ed03f567dc5daafd083e52308e82b, a regression through the real mobile outbox drain reproduced the lost-reply bug: after the shell contained the created thread, the composer changed from Keep this draft separate to Keep this draft separate\nKeep this queued.
Our alternative branch at bb905486a1f46892e361294ad704c639c068a17e passed 95 focused tests across five files and vp run check:changed. Coverage included lost replies, encoded-storage hydration into a fresh outbox manager, preserving saved edits, a second lost reply leaving the task queued, and deletion/access changes during persistence. The final precommit tested diff matches that commit. These results apply to our branch; I have not tested this PR's head.
One useful source finding: ThreadLaunchService commits thread.create and the initial message.dispatch separately. The existing "claimed-only" server test covers a shell without its initial message, so shell presence alone cannot prove acceptance. Reusing the launch command and message IDs lets server receipts either confirm acceptance or finish the interrupted launch.
Native attempt on physical shiv targeted the Simlock iPhone 16 simulator, iOS 27.0, at base 4cb6945dc60ed03f567dc5daafd083e52308e82b. The arm64 Debug build succeeded with Xcode 27.1, native fingerprint b517b3278a179c09a79266873d97f3514e87af91, but installation failed with INVALID_ARGS: install requires an active session or an explicit device selector. The installer was invoked outside the leased session's scratch workspace. After your takeover we let the build finish and cleaned up without retrying. No app was installed or paired, no native scenarios ran, and there are no before/after recordings. Device reproduction and native fix verification remain unproven.
Environment for both recordings: pooled iPhone 18 Pro iOS simulator, light theme, 402×874 logical viewport, real development client, separate worktree/Metro/server per commit, fresh synthetic Sample project, identical Replay scenario.
Steps: enter Please say hello once. in a new thread and tap Send once. The proxy forwards the launch, receives success, drops that reply, and blocks reconnects. Restore connectivity approximately six seconds later without restarting or editing the queued message. Wait twenty seconds and inspect the composer. No second Send tap.
AI-assisted; verified with the tests and tools listed above.
Not shown:
Preservation of an unrelated composer draft was not exercised; both comparison runs used an initially empty composer.
Edited queued messages, app-restart recovery, and pull-request/worktree launches were not exercised; the recorded reproduction covers an unchanged current-checkout creation while the app stays running.
Retry command-ID equality and workspace reuse were not captured directly; the recordings establish the visible outcome and the server checks establish one user message.
Android and physical-device behavior were not exercised; this proof covers the reported iOS flow.
* fix(server): Claude can show the user mid-turn text in full (pingdotgg#36384)
* fix(cli): tagged source builds report the release version (pingdotgg#36350)
Co-authored-by: Lakshmi Tanmay <[email protected]>
* fix(server): storage cleanup prunes idle No project folders under ~/.t3/scratch (pingdotgg#36369)
* fix(terminal): trimmed history no longer replays half of a control sequence (pingdotgg#36368)
* fix(usage): a re-emitted Codex snapshot is no longer billed as a counter rewind (pingdotgg#36367)
Co-authored-by: rihanaws <[email protected]>
* fix(server): a Claude Monitor line no longer wakes a delegated child's parent (pingdotgg#36339)
* fix(opencode): a lost subagent report no longer leaves a resumed call running (pingdotgg#36386)
* fix(web): a new worktree thread opened during a clone bases on the real default branch (pingdotgg#36379)
* fix(server): unauthenticated token requests no longer write DPoP replay markers (pingdotgg#36393)
* fix(server): other sites can no longer frame the web app (pingdotgg#36395)
* fix(server): a server browser tab survives a renderer crash, and a service worker badge call no longer causes one (pingdotgg#36389)
* fix(web): T3 Connect sign-in offers Retry after Clerk fails to load (pingdotgg#23620)
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(ci): macOS PR previews no longer run the PR's bundle next to signing secrets (pingdotgg#36407)
* fix(mobile): a new thread's first message no longer returns to the composer after a lost launch reply (pingdotgg#32330)
* fix(web): size markdown media whose width and height carry a px unit (pingdotgg#32108)
* fix(web): file links the agent cited from a checkout it cd'd into open that file (pingdotgg#36364)
* ci(mobile): iOS jobs run on macos-27, store builds require the final Xcode 27.1 (pingdotgg#36421)
* fix(server): Claude context meter no longer shows an auto-compact point when auto-compaction is off (pingdotgg#36417)
* fix(web): a run waiting on foreground subagents says so instead of Thinking (pingdotgg#36334)
* fix(server): a follow-up to a delegated child waits for its awaited background work (pingdotgg#36411)
* fix(provider-grok): Grok follow-ups queue instead of cancelling the running tool (pingdotgg#36422)
* fix(server): thread list builds metadata only for the page it returns (pingdotgg#36434)
* fix(web): middle-clicking empty chat space on Linux no longer pastes into the composer (pingdotgg#36436)
* fix(web): closing Settings no longer leaves it as a back/forward stop (pingdotgg#36437)
* fix(opencode): OpenCode 1.x threads report context-window usage (pingdotgg#36430)
* fix(web): bare @ in the composer lists workspace files (pingdotgg#36435)
* fix(mobile): thread list project filter lists recently active projects first (pingdotgg#36433)
* fix(clients): live working timers count on the host's clock (pingdotgg#36420)
* perf(relay): lighter Postgres load and PS-160 cluster (pingdotgg#36442)
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(mobile): mobile:install --session works on pooled devices (pingdotgg#36447)
* fix(native): KDE SnapShot no longer loses the first reply from KWin (pingdotgg#36218)
Co-authored-by: Claude Fable 5.2 <[email protected]>
* fix(server): t3 connect status no longer reports a publish-only link as exposed (pingdotgg#33967)
* fix(web): don't label Windows paths outside the workspace as relative (pingdotgg#28469)
Co-authored-by: Julius Marminge <[email protected]>
* fix(server): multi-line guest messages are no longer refused or stored with CRLF (pingdotgg#26542)
* perf(desktop): provider checks no longer compete with server startup (pingdotgg#35712)
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(web,mobile): explain missing file access for host videos (pingdotgg#24637)
* fix(scripts): iOS scenarios prepare the dev client before leasing a device (pingdotgg#36456)
* perf(server): a new thread's first turn starts sooner (pingdotgg#35710)
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(provider-pi): running shell output no longer stores a snapshot row per update (pingdotgg#36429)
* fix(web): NVDA browse mode reads the current thread's branch, not the previous one (pingdotgg#34446)
Co-authored-by: nibarAhmed <[email protected]>
Co-authored-by: Julius Marminge <[email protected]>
* fix(server): T3 Connect clients are named "T3 Connect", not "T3 Connect connect" (pingdotgg#27887)
* fix(web): screen readers read the Update everything machine list as a table (pingdotgg#36445)
* feat: agents can restyle T3 Code with theme colors and CSS (pingdotgg#35247)
Co-authored-by: maria-rcks <[email protected]>
Co-authored-by: Claude Fable 5.2 <[email protected]>
* fix(mobile): long-pressed iOS blockquotes no longer draw over the thread (pingdotgg#36461)
* fix(cli): npm launcher no longer leaves a runtime running after a stop signal (pingdotgg#30487)
* feat(connect): require approval for new T3 Connect devices (pingdotgg#36419)
Co-authored-by: Claude Opus 5.5 <[email protected]>
* fix(server): run the Azure CLI in UTF-8 on Windows (pingdotgg#25801)
Co-authored-by: Julius Marminge <[email protected]>
* fix(server): preview navigation leaves a page whose script never yields (pingdotgg#36365)
* fix(server): the auto-settle sweep no longer rereads every idle branch every few minutes (pingdotgg#36371)
* fix(web): rewind no longer offers file restore in project-folder threads (pingdotgg#36467)
* fix(server): stop the relay client a crashed server left running (pingdotgg#36465)
* fix(server): read Azure DevOps pull request templates (pingdotgg#22881)
* perf(azure-devops): PR Code tab loads a page's files in one az run instead of two per file (pingdotgg#35295)
Co-authored-by: Julius Marminge <[email protected]>
* fix(web): copying a file preview selection returns the selected lines after scrolling (pingdotgg#36466)
* fix(mobile): push-token failures no longer report notifications as enabled (pingdotgg#26557)
* fix(provider-core): mise updates a tool configured by its backend name (pingdotgg#36472)
* fix(server): parent turns no longer duplicate delegated task thread items (pingdotgg#36473)
* fix(server): Windows drive paths open under WSL (pingdotgg#26030)
* fix(client-runtime): new T3 Connect machines appear without signing in again (pingdotgg#29788)
* fix(azure-devops): local pull request checkout no longer fails on --detect (pingdotgg#31170)
Co-authored-by: maria-rcks <[email protected]>
Co-authored-by: Julius Marminge <[email protected]>
* fix(usage): count shared Windows and WSL transcripts once (pingdotgg#24319)
* fix(azure-devops): pull requests report build validation as checks (pingdotgg#31146)
Co-authored-by: maria-rcks <[email protected]>
Co-authored-by: Julius Marminge <[email protected]>
* fix(desktop): WSL backend starts in distros without automounted drives (pingdotgg#25734)
* fix(server): t3 browser setup reloads an AppArmor profile that is on disk but not loaded (pingdotgg#36490)
* Initial plan
* fix(azure-devops): finish reconciling nightly diff tests
Remove retired files-diff code and update mocks for iteration changes and blob reads. Preserve bounded paging, cache, and error-path coverage.
Co-authored-by: Copilot <[email protected]>
---------
Co-authored-by: Julius Marminge <[email protected]>
Co-authored-by: Lakshmi Tanmay <[email protected]>
Co-authored-by: rihanaws <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Theo Browne <[email protected]>
Co-authored-by: ValeraZSD <[email protected]>
Co-authored-by: nibarAhmed <[email protected]>
Co-authored-by: nibarAhmed <[email protected]>
Co-authored-by: maria-rcks <[email protected]>
Co-authored-by: Pedro Henrique <[email protected]>
Co-authored-by: Kieren-Foenander <[email protected]>
Co-authored-by: Pavel Kroupa <[email protected]>
Co-authored-by: copilot-swe-agent[bot] <[email protected]>
Co-authored-by: Alex Vallone <[email protected]>
Co-authored-by: Copilot <[email protected]>
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
bugfixFix PR opened by an issue lead; enters the PR looppr:gate-failedA land gate failedsize:S10-29 changed lines (additions + deletions).vouch:trustedAuthor is trusted by repo permissions or the VOUCHED list.
2 participants
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.
Problem
On mobile, the first message of a new thread could come back into the thread's composer after a network drop, even though the server had already received it. If the launch reply was lost, the outbox kept the queued task. Once the shell listed the new thread, the drain treated the task as edited after delivery and merged its text into the composer, so tapping Send again would duplicate it. The issue includes a server trace from a real device.
Change
The thread outbox delivery service now records the revision and workspace of each new task's launch, in memory only. If the shell lists the thread but the queued task is unchanged since that launch, the drain resends the launch instead of handing the text back. The resend reuses the original command id, message id and workspace. The server's launch is idempotent by command id: it replays a completed launch from its receipt, and dispatches the initial message only when that message's own receipt is missing. So a resend either confirms the launch or finishes one that stopped between creating the thread and accepting the message. The normal success path then clears the outbox without touching the composer.
A resend goes through the same gates as the first launch (live shell, connection, operation access). It reuses the launched worktree instead of preparing the pull request checkout again. A task edited after its launch, or one launched before an app restart (no in-memory record), is still handed back to the composer as before.
Scope and approval
Fixes #32313
Verification
Focused tests in
apps/mobile/src/state/threadOutboxDelivery.permissions.test.ts, describe "queued task whose launch reply is lost". They drive the real drain: the launch returnsSocket is not connectedafter the server created the thread, the shell then lists the thread, and the retry timer fires.resends an unedited launch instead of handing its text back: fails on main withexpected 'Keep this draft separate\nMy dictated…' to be 'Keep this draft separate', the reported symptom. Passes here: one resend with the same command and message ids, composer untouched, outbox empty.hands back a task edited while its launch was in flight: passes on main and here. It pins the intended hand-back.holds the resend while operation access is missing: no dispatch while access is missing, then one resend once it returns.resends a pull request task in its launched worktree without checking out again: one checkout, and the resend reuses the same bootstrap.threadOutboxDelivery.permissions.test.tsandthreadOutboxDelivery.test.tspass, 45/45.vp run check:changedpasses on the final head: format, lint, knip, typecheck, tests and react compiler.Runtime evidence against a real server
The bug needs the server to commit a launch and the client to lose its reply. A driver runs each side's real mobile delivery service, outbox and composer code against that side's own dev server (fresh database, synthetic data) through a proxy that forwards the launch, holds the successful reply, then drops it and closes the socket. The client reconnects and receives the thread in the server's shell snapshot. Before = e5b0718a8a (main), after = 5551099eb1 (this fix; later commits only merge main). Environment: Linux x86_64, Node 26, non-UI runtime; no render code changed.
My dictated ideareturns to the composerKeep this draft separateOn every run the server holds exactly one user message and one turn, and the persisted outbox ends empty. What else changed: generated ids, timestamps and ports only. Full report with proxy logs, server counts and step timestamps: evidence.md. Scripts: run-evidence.mjs, runtime.template.ts, drop-proxy.mjs, query-counts.mjs. Fixture matrix: matrix.md; the other rows are covered by the tests above or unchanged.
Not checked on a device: the pooled emulator was unavailable, so native rendering and the connection supervisor were not exercised; the transcripts replace them with adapters that keep the delivery decision, composer merge and outbox removal real. Not changed: a launch whose reply was lost, followed by an app restart before the retry, still hands the text back. Without in-memory state, the client cannot tell that payload from an edit.
Verification log
AI-assisted; verified with the tests and tools listed above.
🤖 Generated with Claude Code