You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The wire grew a {"request_id", "command"} CommandRequest envelope for commands that expect a reply. A client built for that envelope talking to a pre-#348 daemon gets the frame rejected as an undecodable bare command — the dispatch never runs, the reply never comes.
The queued-message edit path is the sharp edge: AppState::edit_queued_message removes the optimistic queued-user-{session}-{entry} echo and reports "Queued message removed" before any dispatch confirmation. Against an older daemon (or a rollback) the entry can stay queued and eligible for execution while the UI says it was recalled. Plain cancels have the same optimistic pattern on bare commands, but the enveloped edit path makes it reachable on new client + old daemon.
Already mitigated: command_request times out after 30s and drops the pending intent rather than parking forever.
What's missing is a way to know the peer's capability before carrying out the flow:
Option A — handshake: announce a protocol version or feature flags during attach (in the ?since= URL or a first-frame hello) and gate the envelope paths on it.
Option B — optimistic but honest: keep issuing the request optimistically, but surface an "unconfirmed" state on the row until a CommandResult, a journaled QueuedEntryCancelled, or the timeout resolves it — never claim removal before then.
Activity