send_message deterministically fails with "No action selected" when the target chat's row sits below the sidebar viewport
Summary
POST /api/messages/send fails every time for a chat whose row in the Chats sidebar is scrolled out of view (e.g. a contact with little or no recent activity, sitting at index ~60 of ~80 sessions). The failure is deterministic, not flaky: retries never succeed until the chat moves near the top of the list (i.e. receives a message). Sends to chats near the top of the list work normally on the same install at the same time.
Root cause
ChatOpenState::identify (packages/agent-server-rust/src/ia/states/chat.rs) distinguishes chat from chat_open by whether a visible list[name="Chats"] > list-item carries the SELECTED a11y state:
// ChatState: identified when no selected item is visible
if find_selected_chat_item(args.a11y).is_some() { return ... identified: false ... }
// ChatOpenState: identified when a selected item is visible
if find_selected_chat_item(args.a11y).is_none() { return ... identified: false ... }
The a11y tree only exposes the list items currently in the viewport (~11 rows). When chat-select selects a session whose row is below the viewport (via the selectSession hook redirect), the chat pane opens correctly — the a11y tree contains the Send(S) button, the edit box, and the chat header — but no visible list item is SELECTED, so the main window is identified as chat, never chat_open.
SendMessagePlan's Focusing phase requires main_state_id == Some("chat_open") and returns None otherwise (packages/agent-server-rust/src/plans/send_message.rs), which surfaces as the generic "No action selected". The Opening phase succeeds (or skips via current_sel == target), so the plan reaches Focusing and then fails on every step, forever.
Reproduction
- A logged-in account with enough sessions that some rows sit below the sidebar viewport (we had 80).
- Pick a target chat with old/no activity so its row is well below the fold.
chat-select --list shows it present; chat-select --force <id> returns {"ok": true, ...} and the chat pane visibly opens.
POST /api/messages/send for that chat → {"success": false, "error": "No action selected"} every attempt.
- Same call for a chat near the top of the list succeeds.
Evidence from our install
/api/debug/a11y sampled 15× at 2s intervals while the target chat was open: stable 13.4 KB tree, Send(S) present, edit box present, list[name="Chats"] exposing 11 items, none SELECTED (the selected row is off-viewport). The tree is healthy; only the state classification is wrong.
- Execution logs during a send show the signature oscillation:
step=0 mainWindow=chat_open (transiently, while the click used to fire the hook lands on a visible top row) followed by step=1 mainWindow=chat → receipt fails ~30ms later.
chat-select stderr confirms the selection itself works: Current selection: <target>, hook fired, click landed.
Suggested fixes (any one of these would resolve it)
- Identify
chat_open by chat-pane features instead of the sidebar: presence of the message edit box + Send(S) button (the pair find_edit_and_send_button already locates), or the chat header label. That reflects what the state actually means — a conversation is open — and is viewport-independent.
- Or: after the
selectSession hook redirect, scroll the sidebar so the selected row is in view before returning from chat-select.
- Or: let
SendMessagePlan::Focusing accept the chat state when find_edit_and_send_button succeeds — the button pair is itself proof that a chat is open.
Happy to test a patch against our install.
send_message deterministically fails with "No action selected" when the target chat's row sits below the sidebar viewport
Summary
POST /api/messages/sendfails every time for a chat whose row in the Chats sidebar is scrolled out of view (e.g. a contact with little or no recent activity, sitting at index ~60 of ~80 sessions). The failure is deterministic, not flaky: retries never succeed until the chat moves near the top of the list (i.e. receives a message). Sends to chats near the top of the list work normally on the same install at the same time.Root cause
ChatOpenState::identify(packages/agent-server-rust/src/ia/states/chat.rs) distinguisheschatfromchat_openby whether a visiblelist[name="Chats"] > list-itemcarries theSELECTEDa11y state:The a11y tree only exposes the list items currently in the viewport (~11 rows). When
chat-selectselects a session whose row is below the viewport (via theselectSessionhook redirect), the chat pane opens correctly — the a11y tree contains theSend(S)button, the edit box, and the chat header — but no visible list item is SELECTED, so the main window is identified aschat, neverchat_open.SendMessagePlan'sFocusingphase requiresmain_state_id == Some("chat_open")and returnsNoneotherwise (packages/agent-server-rust/src/plans/send_message.rs), which surfaces as the generic "No action selected". TheOpeningphase succeeds (or skips viacurrent_sel == target), so the plan reachesFocusingand then fails on every step, forever.Reproduction
chat-select --listshows it present;chat-select --force <id>returns{"ok": true, ...}and the chat pane visibly opens.POST /api/messages/sendfor that chat →{"success": false, "error": "No action selected"}every attempt.Evidence from our install
/api/debug/a11ysampled 15× at 2s intervals while the target chat was open: stable 13.4 KB tree,Send(S)present, edit box present,list[name="Chats"]exposing 11 items, none SELECTED (the selected row is off-viewport). The tree is healthy; only the state classification is wrong.step=0 mainWindow=chat_open(transiently, while the click used to fire the hook lands on a visible top row) followed bystep=1 mainWindow=chat→ receipt fails ~30ms later.chat-selectstderr confirms the selection itself works:Current selection: <target>, hook fired, click landed.Suggested fixes (any one of these would resolve it)
chat_openby chat-pane features instead of the sidebar: presence of the message edit box +Send(S)button (the pairfind_edit_and_send_buttonalready locates), or the chat header label. That reflects what the state actually means — a conversation is open — and is viewport-independent.selectSessionhook redirect, scroll the sidebar so the selected row is in view before returning fromchat-select.SendMessagePlan::Focusingaccept thechatstate whenfind_edit_and_send_buttonsucceeds — the button pair is itself proof that a chat is open.Happy to test a patch against our install.