fix(cmux-tui, cmux NEXT): the daemon answers a sidebar move promptly; a timeout toast says why in words
Leo, on the toast "Move did not apply: set-personal-workspace: timed out: workspace.place (no reply within 2.0 seconds)": "idk what happened here".
Cause
The app's log at the failed move shows every request to the home cmux-tui daemon timing out together (window_record.put, workspace.place, close-workspace, new-conversation-tab, put-frontend-projection, create-terminal). In the same minutes, the daemon logged slow journal maintenance: 5-8 s (slowest agent_roster) on every tick, about once a minute. So the daemon was blocked; the move itself was not slow.
The roster catch-up reads the journal tail except terminal.output with kind < 'terminal.output' AND sequence > ? on the (kind, sequence) index. A range on the first column cannot seek on the second, so each 512-record page walked every non-output entry the active journal held: 5.6M on this nightly registry. The fold lock and the I/O sit in front of agent hook appends and other requests, which then missed the 2 s deadline.
Fix
query_session_journal_without_output_afterseeks once per kind: a recursive skip scan lists the distinct kinds from the same index (61 here), then joins a(kind = ?, sequence in window)seek for each. A bounded sequence window (8,192) caps how many matches one page sorts. A short page reports the window's end as covered. Every caller already loops until it reaches the head (roster fold, roster restore, history feed).- A command that misses its deadline now shows "cmux's background service was busy and did not answer in time, so it may still apply." (21 languages) instead of the command names. Callers (the control socket, the CLI) keep the raw error in
message. This covers every action toast, not just Move.
Performance
| metric | before | after | delta | harness |
|---|---|---|---|---|
| one 512-record non-output page, read-only on the 58 GB nightly registry (warm) | 330 ms | 5 ms | -98% | sqlite3 CLI, same rows returned |
| agent_roster maintenance step in the daemon | 5-8 s per tick (daemon log) | expected to be in milliseconds | n/a | the next nightly's client.log |
Risk: SQL read path of the roster and history folds. The new test pages through 16k records with a run of output longer than one window and checks that each non-output record comes back exactly once, in order.
Tests: non_output_read_pages_every_other_kind_across_seek_windows (Rust) and DaemonTimeoutToastCopyTests (Swift).
Follow-up (not in this PR): the nightly registry is 58 GB with a 15 GB WAL. That growth is its own issue.
Agent session: agent session
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.
Summary by cubic
Fixes the daemon answering slowly by making the roster catch-up's journal read seek once per kind, and replaces raw command names in timeout toasts with a human-readable reason.
Bug Fixes
- The non-output journal read previously ranged on the
(kind, sequence)index's first column, so each page walked every non-output entry the active journal kept (5.6M), blocking the daemon for 5–8 s per maintenance tick and making requests miss their 2 s deadline. It now lists distinct kinds via a skip scan and seeks per kind inside a bounded sequence window (8,192), cutting a 512-record page from 330 ms to 5 ms on a warm nightly registry. - A command that times out now shows "cmux’s background service was busy and did not answer in time, so it may still apply." in 21 languages instead of command names like
set-personal-workspace: timed out: workspace.place. Callers keep the raw error inmessage.