Commit e3b2865
authored
fix(tui): stop cancel-then-resend from racing the daemon's session lock (#2912)
Closes #2901
Stacked on #2899 (base branch: `tmi/issue-2899-tui-stats`) — not `main`.
## Why this matters
Cancelling a turn (Esc/Ctrl+C) used to free the composer immediately,
letting a follow-up query go out on the same session before the daemon
had actually finished the previous one — the daemon only releases its
per-session lock when the sidecar's worker thread notices the cancel and
unwinds (cooperative, checked once per agent-loop step), not when the
client drops its connection. A fast enough retype could beat that
release and land on a bare, confusing 409.
Live testing on real hardware then found the fix itself had a worse
problem: a **second** cancel-then-resend in the same session quit the
app outright (3/3, clean exit, no panic). Root cause: a cancelled turn
on the daemon transport never actually settles via `doneMsg` — it
settles via its own terminal event (the server's cooperative cancel just
produces an ordinary "stopped" answer), and that code path never rearms
the listener `doneMsg` depends on. The turn's cancel bookkeeping was
left stuck "pending" for the rest of the session, so the *next*
Esc/Ctrl+C failed its own guard and fell through to quit. Fixed —
settlement now happens on every real terminal signal for the live turn,
not only the one that could never arrive.
**Scope note:** live testing also measured input staying blocked ~18–25s
per cancel while the server's cooperative cancellation finishes whatever
step is already in flight. That is server-side latency, not something
this client-side fix can shorten, so it is split out to #2917 with a
recommended direction (queue the resend instead of blocking on it)
rather than folded in here.
When a conflict does still happen (a genuinely concurrent request), the
client reports it with an actionable message naming the busy session,
instead of the generic "check daemon status" copy meant for an
unreachable daemon.
The server-side 409 policy itself is intentional and untouched
(`hub/agents/email/python/gaia_agent_email/query_routes.py`) — it is
documented, tested behavior that prevents two turns from stepping on one
session's agent state. This PR only closes the client-side windows that
let a resend either race that lock or crash the app.
## Live evidence
- **409s:** 0 of 8 cancel-then-resend attempts (down from 5/5 before
this PR).
- **Second-cycle crash:** 3/3 reproducible before this fix, closed by a
failing-test-first regression
(`TestSecondCancelThenResendDoesNotQuitTheApp`,
`TestSecondCtrlCThenResendDoesNotQuitTheApp`) that fails against the
pre-fix code and passes after.
- **Input-blocking (~18–25s per cancel):** confirmed real, not fixable
client-side alone — tracked in #2917.
## Test plan
- [x] `cd tui && go test ./...` — all packages pass
- [x] `go test -race ./internal/ui/chat/... ./internal/client/...` —
clean
- [x] New regression tests reproduce the live two-cycle crash end to end
(cancel turn 1 → settle via its own terminal event → resend turn 2 →
cancel again) and fail against the pre-fix code, pass after
- [ ] Live-mailbox validation (cancel-then-resend, including the
two-cycle case) — scheduled separately1 parent b38f5e6 commit e3b2865
10 files changed
Lines changed: 1007 additions & 53 deletions
File tree
- tui/internal
- client
- ui/chat
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
36 | 36 | | |
37 | 37 | | |
38 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
39 | 59 | | |
40 | 60 | | |
41 | 61 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
236 | 236 | | |
237 | 237 | | |
238 | 238 | | |
239 | | - | |
| 239 | + | |
| 240 | + | |
240 | 241 | | |
241 | 242 | | |
242 | 243 | | |
| |||
246 | 247 | | |
247 | 248 | | |
248 | 249 | | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
249 | 265 | | |
250 | | - | |
251 | | - | |
252 | | - | |
253 | 266 | | |
254 | 267 | | |
255 | 268 | | |
| |||
588 | 601 | | |
589 | 602 | | |
590 | 603 | | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
591 | 619 | | |
592 | 620 | | |
593 | 621 | | |
| |||
605 | 633 | | |
606 | 634 | | |
607 | 635 | | |
608 | | - | |
609 | | - | |
610 | | - | |
611 | | - | |
612 | | - | |
613 | | - | |
614 | | - | |
| 636 | + | |
615 | 637 | | |
616 | 638 | | |
617 | 639 | | |
| |||
632 | 654 | | |
633 | 655 | | |
634 | 656 | | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
635 | 705 | | |
636 | 706 | | |
637 | 707 | | |
| |||
782 | 852 | | |
783 | 853 | | |
784 | 854 | | |
| 855 | + | |
785 | 856 | | |
786 | 857 | | |
0 commit comments