# Socket-send retry: proof audit pass 1

Scope: read-only source audit of /home/ubuntu/wt/issue-24659, head 2703941773 versus c79ef8c454. Full diff: only commands.ts and commands.test.ts in packages/client-runtime/src/operations. No source edits, tests, browser use, captures, server starts, or PR operations performed. This is a proof plan, not executed proof. Lead owns the healthy server on port 5778.

## Minimum matrix and capture policy

16 affected fixture rows below; every row requires a recording because ordering, pending duration, retry, or interaction is the behavior. One continuous recording may contain multiple clearly labeled rows; 16 independently identifiable recording segments are required, not necessarily 16 files. Record before/after for the defect reproduction (row 2) on base and head: minimum 17 segments total. Other rows need head recordings with wire/persisted-state evidence. Still screenshots alone cannot prove any row.

No CSS or render code changes. Minimum responsive/theme set is 1440 light and 390 dark. Run core transport rows 1–7 at 1440 light; repeat row 2 at 390 dark, giving 18 minimum segments including the base segment. This intentionally covers both requested widths and both themes without a four-way Cartesian product: retry logic, markup, styles, and theme decisions are unchanged. It does NOT claim 1440 dark or 390 light were executed. If the request requires all four exact combinations rather than width/theme coverage, add row 2 at those two combinations (20 segments total). Native mobile dimensions follow its actual device, not a 390px browser impersonation. Web at 390 does not verify native mobile.

All rows start from real app state in the lead's isolated dev home, never the live ~/.t3/userdata database. Reuse existing stopped, seeded threads or have the lead copy a read-only snapshot with migrate-dev-db only while their server is stopped; do not run migration against the healthy running server. Create fixtures through real app/service paths. Use harmless prompts in a dedicated project with an available provider. Deterministic fault injection must isolate the test client's socket and identify message.dispatch, not break port 5778 or kill the server. Devtools offline toggling alone cannot distinguish pre-commit loss from lost acknowledgement. Label request commandId, messageId, session identity, attempt count, and final persisted message/run count alongside the recording. No credentials/customer data in artifacts.

| ID | Real app seed / entry point | Interaction and fault | Required observable result / recording |
|---|---|---|---|
| 1 | Existing stopped thread, composer with unique prompt, one small attachment and context reference already uploaded | Healthy send via button; keyboard send may share this fixture | One message/run, cleared pending state; attachments/context/model unchanged. No resend. |
| 2 | Same stopped thread with new unique prompt | Lose first dispatch before commit; hold disconnection visibly, reconnect with a new session | Pending survives; one resend with same commandId/message payload, one durable message and one run, normal completion; base/head comparison. |
| 3 | Existing stopped thread, unique prompt | Commit first dispatch, suppress only its acknowledgement, close socket, reconnect | Receipt replay: two wire attempts, exactly one durable message/run/effect; pending settles even if projection arrives before receipt. A mock accepting twice is insufficient. |
| 4 | Existing stopped thread, unique prompt | Drop attempts 1 and 2 on distinct sessions; attempt 3 succeeds | Exactly three total attempts; unchanged commandId/body; pending settles once. |
| 5 | Existing stopped thread, unique prompt | Drop all three attempts across distinct sessions, then allow another reconnect | Final transport error after third failure, no fourth send; composer/error and optimistic message reconcile. Do not assert mobile outbox never retries later: its outer retry is separate. |
| 6 | Existing thread lacking dispatch permission or a deterministic server business rejection after settings have succeeded | Submit and receive typed non-RpcClientError rejection; reconnect afterward | Immediate error/restore behavior and one dispatch only; no deferred resend. Verify exact error class rather than assuming every rejection is business error. |
| 7 | Existing stopped thread, in-flight lost dispatch awaiting new session | Exercise actual UI cancel/Stop if offered, then reconnect; separately navigate away while pending | Establish whether the command fiber is interrupted and whether a resend happens. No silent late send after an acknowledged cancel. Navigation is not inherently cancellation. If there is no cancel affordance, show that fact and prove fiber interruption with a focused Deferred/Fiber test. Intended cancel semantics require maintainer agreement. |
| 8 | Existing thread with provider actively running a harmless long task, ordinary composer | Send auto/steer and lose acknowledgement; repeat using interrupt/restart mode on a capable provider | Delivery intent/target run preserved; one steer or one restart, no duplicate turn. Modern server intent resolution and legacy projection-resolved modes need contract tests; recordings can share the busy-thread scene. |
| 9 | Same busy thread, queue dropdown; also installed app panel with send-message action (ChatView.sendAppMessage) | Queue composer message and app-origin message with one lost dispatch each | Exactly one queued item per action; no unexpected immediate start; app action promise settles. Cancel queued item and show it stays cancelled after reconnect. |
| 10 | Completed/interrupted resumable thread with Resume available and held queue if supported | Resume via useComposerResume, force one reconnect during explicit start dispatch | Explicit-start branch preserves manualContinuationOfRunId, resumes once, clears resuming indicator and releases held queue only after success. |
| 11 | Existing thread exposing standalone lifecycle action such as compact; also composer with compact-before-send enabled | Trigger standalone action, then compact-before-send flow; lose compact acknowledgement | One compact; follow-up prompt sent once after compact acknowledgement; optimistic lifecycle row/pending/error state settles. Seed a long enough history for compaction eligibility, not an empty DB. |
| 12 | Thread with a persisted proposed plan and its implementation actions | Implement in same thread, then implement in a newly created thread; fault dispatch after create succeeds | Both use retry path; one implementation message per action, source plan reference retained, no duplicate new thread; plan pending controls settle. New-thread implementation explicitly creates then dispatches, unlike bootstrap launch. |
| 13 | Native iOS app paired to lead's isolated environment, existing stopped thread and local outbox entry with unique text/attachment | Send with default explicit start; lose committed acknowledgement and reconnect | One durable message/run, stable outbox commandId, pending task removed once, no draft/attachment loss; capture native interaction and outbox transition. |
| 14 | Native Android app paired to same isolated environment, existing busy thread; outbox queue delivery selected | Queue send through reconnect, then cancel/remove pending task during another lost-send window | Queue and outbox reconcile without duplication or resurrecting intentionally removed task; clarify whether removing local pending task cancels an in-flight server dispatch. Do not equate local removal with server cancellation without proof. |
| 15 | Desktop Electron client connected to isolated environment, existing stopped thread | Lost committed acknowledgement and reconnect through ordinary composer | Same shared web sending state settles once in Electron; one message/run. Browser web cannot prove desktop shell lifecycle behavior. |
| 16 | Web client connected remotely/relay or tunnel to isolated environment, existing stopped thread | Lost dispatch/reconnect without restarting host | Correct destination environment, same commandId on new session, no localhost baked origin, one message; recording includes connection recovery. Local web alone cannot prove this mode. |

## Caller/render-path accounting

Zero changed render functions; affected existing presentations are composer sending/draft restoration and error, optimistic timeline rows, queued rows, Resume pending indicator, standalone lifecycle actions, plan implementation pending/navigation, app message action completion, and native pending outbox tasks. Rows 1–12 cover web and shared desktop code paths; row 15 verifies the Electron runtime. Rows 13–14 cover separate native callers in use-thread-outbox-drain.ts. Browser width is not native proof.

Changed branches: explicit dispatchMode=start (row 10/native default) and auto/queue/steer/restart path (remaining web sends/native queue). Attachments upload before dispatch and must not reupload during retry (row 1 combined with row 2 or 3).

Unchanged bypasses: bootstrap.createThread / prepareWorktree returns launchThread before dispatchMessage. Ordinary first send into a local draft, worktree preparation, multi-model fanout (useMultipleModelSend), and mobile new-project-thread creation therefore do NOT gain this retry. Guest-message Send (resolveGuestMessage) still calls raw dispatch(message.dispatch). These are spec-scope decisions, not proof of a fixed path. If issue intent is all socket-lost message sends, these are coverage gaps. Add separate regression/control recordings for these routes only if claiming they are fixed; otherwise explicitly exclude them from the claim. Scheduled tasks/MCP server sends do not use this client helper and are not changed.

Provider matrix can collapse for transport retry itself: envelope and receipt replay are provider independent. Use one working provider for idle/start/queue. For steer and restart use actual provider capability support (may require a second provider); do not infer unsupported modes from a generic successful send. No need to record all six providers when adapters are unchanged.

## Proof sufficiency and specification gaps

The added tests show same commandId on one mocked reconnect and a three-attempt ceiling. They do not demonstrate real server receipt replay or a persisted single message/run/effect after lost acknowledgement. The first mock pushes the command but does not commit it. Neither added test explicitly exercises dispatchMode=start. No captures or test execution results were supplied to this audit; proof is currently insufficient.

Focused missing tests: typed business rejection/no resend; successful third attempt; no retry on same-session-only update; new session already present before waiter attaches; explicit-start branch; interrupt while awaiting reconnect; real receipt replay; modern and legacy dispatch modes preserving full payload. Use Deferred and Fiber milestones, not sleeps. Pre-dispatch upload/settings/projection/launch failures are outside this helper and must not be mislabeled retry failures.

Implementation retries every RpcClientError, not specifically a socket-close reason. A protocol/serialization/client defect without session replacement can leave the send pending indefinitely. The two-resend bound limits attempts, not elapsed waiting; no new session means no timeout. Product requirements must state whether indefinite pending is desired, how users cancel it, and whether non-close RpcClientError is in scope.

The snapshot of attemptSession is taken before request() independently obtains currentSession(). A session transition between those reads could make the waiter accept the session that actually failed and resend without a truly newer session. This is a race hypothesis requiring deterministic coverage, not a confirmed reproduced bug.

Server-side deduplication must cover receipt replay before duplicate side effects and surviving projection arrival/reconnect. Reconnect does not imply resend is safe unless the receipt is durable for this exact commandId and destination environment.

## FRICTION

- No source mutation, browser use, capture, test execution, server start, push, merge, watch, or PR wait authorized/performed in this pass. Lead must execute the matrix and attach recordings plus targeted test results.
- A deterministic per-client dispatch/ack fault injector is needed. Ordinary offline mode cannot prove the lost-ack row. No injector was built or installed by this audit.
- Native iOS, native Android, Electron desktop, remote/relay/tunnel, and even local web remain unverified here. Name each absent platform/mode in the proof handoff; do not substitute responsive web for mobile or shared code inspection for desktop.
- Clarify launchThread/guest Send scope, cancellation semantics, infinite reconnect wait, RpcClientError classification, and exact four-combination viewport/theme expectation before declaring the issue fully proved.
- No supplied capture bundle was available to assess; this artifact specifies minimum evidence and must not be presented as a passing verification report.
