Repository navigation
fix(terminal): retry a two-stage send without repeating paste or Enter - #29234
Conversation
4a03be3 to
08525b0
Compare
|
cmux status (one comment, edited in place; each line is updated by the job that owns it) Review App of this head (app.zip, kept 1 day)
Shipped in:
|
|
Review (claude opus, correctness first): SHIP at 08525b0 Rebuilt on feat-cmux-next: #29207's CLI wiring and cli_audit commits were dropped (base already routes Correctness read:
Follow-ups (non-blocking):
Reds at this head are not this PR's: clippy fails on collapsible-if lints in acpmux/cmux-tui-core files this PR does not touch; cmux-next checks fail on xcstrings, the Swift daemon-capabilities export, script/job-structure/tier-routing tests over untouched files; tooling-tests is red on base too. |
…ads use get The ratchet at the merge with feat-cmux-next e27f116 (18cef5a, job 9d666b02dcb4c69dbfc147f0) is still red: cmux-tui indexing_slicing 849 > 831. The 18 new hits after #30414 come from: - d8e1b7c (#29234) cli/terminal_talk/send.rs stage_failure: 2 - 9f5e2e1 (#30634) link/seed_cli.rs: 13, link/mod.rs `&args[1..]`: 1 - 7b30784 (#30664) link/machine_pin.rs: 1, link/mod.rs `&args[2..]`: 1 Each is now a get / pointer-free get read, `JsonSet::set` for the one write, and `args.get(n..)` for the slices (the branch runs only when `args.get(n - 1)` matched, so the slice is always there). A missing key read through get gives None where indexing gave Null: `as_str()` and `as_bool()` see the same, and json! writes None as null. No behavior change; the baseline is not raised.
|
This PR added new |
…0414, #29234, #30634, #30664 and #30574 use checked forms (#30661) * base red: panic ratchet, #30414's new index and string slices in start_chat_cli use checked forms The Rust panic ratchet is red on feat-cmux-next at 7e0a0f2 (tip watcher job 21c6ba0ebda787821cc7bc4b): cmux-tui indexing_slicing 833 > 831 and string_slice 84 > 82. All four hits are from 0507fc4 (#30414) in cmux-tui/src/link/start_chat_cli.rs: - valid_environment: `id[4..]` -> `id.get(4..)`; the id already starts with the ASCII "env_", so the slice is always Some and the same. - github_repo: `&url[scheme.len()..]` -> `split_at_checked`, None in exactly the cases where the `get(..scheme.len())` before it was None. - Ensure::body: `body["ref"] = ..`, `body["environment"] = ..` -> `JsonSet::set` on the same object literal (same insert). No behavior change; the baseline is not raised. * base red: panic ratchet, #29234's, #30634's and #30664's new index reads use get The ratchet at the merge with feat-cmux-next e27f116 (18cef5a, job 9d666b02dcb4c69dbfc147f0) is still red: cmux-tui indexing_slicing 849 > 831. The 18 new hits after #30414 come from: - d8e1b7c (#29234) cli/terminal_talk/send.rs stage_failure: 2 - 9f5e2e1 (#30634) link/seed_cli.rs: 13, link/mod.rs `&args[1..]`: 1 - 7b30784 (#30664) link/machine_pin.rs: 1, link/mod.rs `&args[2..]`: 1 Each is now a get / pointer-free get read, `JsonSet::set` for the one write, and `args.get(n..)` for the slices (the branch runs only when `args.get(n - 1)` matched, so the slice is always there). A missing key read through get gives None where indexing gave Null: `as_str()` and `as_bool()` see the same, and json! writes None as null. No behavior change; the baseline is not raised. * base red: panic ratchet, #30574's new index reads in start_chat_cli use get #30574 (93772c4) landed after the e27f116 merge and added four Value index reads in start-chat plan (line["machine"], ["created"], ["snapshot"], ["environment"]). They are now get reads: a missing key is None where indexing gave Null, as_str() sees the same and json! writes None as null. No behavior change.
What changes
A single
cmux terminal <id> send/ MCPagents_terminal_sendis two daemon mutations: bracketed-paste the text, then Enter. Until now each phase created a fresh random idempotency key. If the daemon applied a write but its acknowledgement was lost, retrying the whole send could paste the text twice or run the shell command twice.This PR gives that composed operation one stable
idempotency_key. The two child mutations receive distinct deterministic SHA-256-derived keys bound to that send ID, resolved terminal and phase. When a write's outcome is uncertain, CLI and MCP return the whole-send key plusdetails.phase; retrying with the key replays the daemon's previously accepted stage result. The complete send receipt also carries its ID. Callers may provide--idempotency-keyin the CLI oridempotency_keyin MCP; without one, the CLI generates a key and returns it on an uncertain write.Rebuilt on
feat-cmux-next(2026-11-01): #29207 was closed because base already routes theterminalverbs throughstatus::run_args -> terminal_talk::run, so this PR's diff is only the send change, the CLI key forwarding, the MCP input and schema, and the tests.Changelog
Tests added
Scope and limitations
--forceas implicit consent.Observed / Not run
Observed: inspected
feat-cmux-next@07088897284b6637a4ddb5763e06ca1601d4aae2and the pushed diff against #29207's headb7a5371a819a0d7fa89191296ee2efa022dc41ff; original research and owner handoff in cmux #23026 and SW-10. The test-only MCP schema commit precedes the implementation.Not run: Rust compiler,
cargo test, real terminal/harness fault injection, hosted verification and independent review. This chat has GitHub write access but no Rust compiler or authorized Testbox. Draft until exact-head tests and review complete.Suggested verification:
./scripts/verify-cmux-tui-hosted.sh --filter terminal_send_retry_replays_both_writes_after_either_lost_ack;--filter terminal_send_generated_key_is_returned_on_a_lost_reply;--filter terminal_send_exposes(or the MCP descriptor test name). Run a full terminal-talk suite and a live same-key retry against an isolated terminal before landing.Need help on this PR? Tag
@codesmith-botwith what you need.Summary by cubic
Fixes retrying a terminal send after a lost acknowledgement so it doesn't paste the text twice or run the shell command twice.
A
cmux terminal <id> send(and the MCPagents_terminal_sendtool) performs two daemon writes: bracketed paste, then Enter. Each write previously used a fresh random idempotency key, so retrying after an uncertain write could apply the same stage twice. Now both writes derive deterministic keys from one stable send identity, and retrying with that identity replays the daemon's previously accepted stage.--idempotency-key/idempotency_key; without one, the CLI generates a key and returns it on an uncertain write.Written for commit 4a03be3. Summary will update on new commits.