acpmux: a chat move whose client goes away ends on its own (cx-xtg0.9)
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 theacpmux transferCLI 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 (noto, 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 theacpmux transferCLI) 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:
tois optional and they get the partial-read abort.