Queued messages wait up to 30s to start — the dispatcher only polls, nothing fires on run completion
What happens
Queue a message while a turn is running. The running turn finishes, the session goes idle, and then nothing happens for up to 30 seconds before the queued turn starts. The reply also arrives in one lump rather than streaming, because a queued turn runs detached.
Why
server/modules/scheduled-messages/services/scheduled-message-dispatcher.service.ts
is the only sender, and its only trigger is a timer:
const POLL_INTERVAL_MS = 30_000;
...
pollTimer = setInterval(poll, POLL_INTERVAL_MS);dispatchQueuedMessages() skips any session where
chatRunRegistry.isProcessing(sessionId) is true, so a message queued during a
turn is necessarily skipped by every tick until the turn ends — and then waits
for the next tick, 0–30s later.
The frontend cannot compensate, by design. useChatComposerState.ts:
// The VPS dispatcher owns sending. While the card is visible, periodically
// reconcile only its removal so the UI notices when the server claims it.so the client polls every 5s purely to notice removal, and never sends.
Nothing subscribes to run completion. chat-run-registry.service.ts flips
run.status = 'completed' in one place and emits no event, so the information
"this session just went idle" exists and is discarded.
Suggested fix
Emit a run-settled event from the registry where the status flips, and have the dispatcher subscribe, keeping the poll as the safety net for messages queued while nothing was running and for restarts. Small diff, no behaviour change for scheduled messages. Happy to open a PR — I have one running locally.
Notes
- Related but different: #1225 (open) — queuing a second message overwrites the first, so only the last survives.
- Related: #1142 — steering was never implemented; queueing sends a new run after the current turn, which is also why the reply does not stream.
Source: siteboon/claudecodeui