acpmux: a chat move whose client goes away ends on its own (cx-xtg0.9)

Merged
TE
1mo ago
+408-18

Summary

A chat being moved (acpmux.transfer.v1) is frozen on its source until the client pumping the move commits or aborts. If that client died (the app quit, the CLI was killed, the machine slept), the chat stayed frozen forever. Now the source watches each move it began and ends a move left idle for the lease (5 min):

  • Not every bundle file was read to its end: no target can have finished (finish checks every file's size), so the move aborts (transfer_aborted {reason: "stalled"}) and the chat runs here again, its queue included.
  • Every file was read: the source asks the target named at begin (new optional to: {socket}, which the acpmux transfer CLI sends) with the new _acpmux/transfer_forget {transferId, sessionId}. The target drops any unfinished receive of the move, marks it so a later receive is refused (transfer.forgotten), and answers {landed}. Landed: the source commits. Not landed: it aborts. No answer (no to, target down, 30 s timeout): the chat stays frozen and the source asks again after another lease.
  • A restarted daemon watches its moves again (state lives in the move's directory: alive, read, to).
  • A page read and the watch's not-fully-read abort share a lock, so a client never gets a bundle's last page from a move that was just given back.

Commit is now idempotent on an already-closed chat. Backward compatible: to is optional, and old clients get the partial-read abort.

Testing

Behavior tests in cmux-tui/crates/acpmux/tests/hub_integration/transfer.rs. Fleet, scripts/ci/cmux-tui-rust-check.sh --arg test|all --arg transfer:::

  • Red 8689ea094f48: FAILED. 2 passed; 3 failed (the three new tests time out; the chat stays frozen).
  • Green f5429bf2a442 (all: fmt, clippy, tests): ok. 5 passed.
  • Review fixes. Red fafd94130af1: FAILED. 4 passed; 1 failed (a forgotten move could be received again). Green 6982494b525d (all): ok. 5 passed.

Crash ratchet: panic_macro 33 -> 35 in cmux-tui, which also fails on the merge base, so it comes from base and not this branch. God-file check: no touched file is listed.

Reviewed by a subagent twice (correctness first). Known edge cases left as-is (all need the client to vanish mid-finish for longer than the lease): a watch commit and a client commit at the same moment can both record; a watch commit whose kill fails is not retried; a chat deleted on the target within the lease reads as not landed.

Changelog

none

closes cx-xtg0.9

🤖 Generated with Claude Code


Summary by cubic

A chat being moved (acpmux.transfer.v1) is frozen on its source until the client pumping the move commits or aborts; if that client dies or goes away, the chat stays frozen forever. The source now watches each move it began and ends a move left idle for the lease (5 min):

  • If the bundle was not read to its end, no target can have finished, so the move aborts (transfer_aborted {reason: "stalled"}) and the chat runs here again, its queue included.
  • If it was, the source asks the target named at begin (new optional to: {socket}, sent by the acpmux transfer CLI) with _acpmux/transfer_forget; the target drops any unfinished receive, refuses later ones, and answers whether the chat landed, which decides commit or abort.
  • If the target can't answer (or no target was named), the chat stays frozen and the source asks again after another lease.
  • State lives in the move's directory, so a restarted daemon resumes watching; repeated commits on an already-closed chat answer committed, and a forgotten move stays refused.
  • Old clients are unaffected: to is optional and they get the partial-read abort.