moeru-ai/airi · warning
[chat-sync] sendMessages failed for
Error message
[chat-sync] sendMessages failed for
What it means
A single queued outbox entry could not be delivered via wsClient.sendMessages; the entry stays queued and the failure is recorded in lastError. Note this path writes attempts: 1 literally instead of incrementing (the batch drain path uses entry.attempts + 1), so failure counts reset on every single-send retry. The outbox drain retries the message later.
Source
Thrown at packages/stage-ui/src/stores/chat/session-store.ts:1154
// Opportunistic immediate send. Skip if WS not open or cloudChatId not
// yet bound — drainOutbox will pick it up on the next reconcile.
if (!wsClient || wsClient.status() !== 'open')
return
if (!entry.cloudChatId)
return
try {
await wsClient.sendMessages({
chatId: entry.cloudChatId,
messages: [{ id: entry.messageId, role: entry.role, content: entry.content }],
})
await enqueuePersist(() => chatSessionsRepo.dequeueOutbox(userId, [entry.messageId]))
await refreshOutboxPendingCount()
}
catch (err) {
const errMsg = errorMessageFrom(err) ?? 'unknown'
console.warn('[chat-sync] sendMessages failed for', sessionId, errMsg)
await enqueuePersist(() => chatSessionsRepo.updateOutboxEntries(userId, [{
messageId: entry.messageId,
attempts: 1,
lastError: errMsg,
}]))
}
}
/**
* Drain every outbox entry for the current user via batched
* `sendMessages` calls (one per session). Idempotent and safe to call
* concurrently — a single-flight guard collapses overlapping triggers.
*
* Drain ordering: entries are grouped by sessionId, sorted by `queuedAt`
* within each session, and sent in a single batch per session. Server
* accepts client-supplied message ids so retries are idempotent.
*
* Entries whose session has no `cloudChatId` yet are skipped (they willView on GitHub (pinned to b6d0809ecb)
Solutions
- Restore connectivity; the outbox drain retries queued entries automatically
- Read entry.lastError from the outbox repo to classify the failure (auth vs unknown chat vs validation)
- If the chat was deleted remotely, force a reconcile so the session re-mints a cloudChatId, then resend
- Fix the attempts accounting: use entry.attempts + 1 (as the batch path does) so chronic failures become visible to eviction logic
- Watch the outbox pending count (refreshOutboxPendingCount-driven UI) so stalled messages are noticed
Example fix
// before
await enqueuePersist(() => chatSessionsRepo.updateOutboxEntries(userId, [{
messageId: entry.messageId,
attempts: 1, // resets the counter on every single-send failure
lastError: errMsg,
}]))
// after: increment like the batch drain path does
await enqueuePersist(() => chatSessionsRepo.updateOutboxEntries(userId, [{
messageId: entry.messageId,
attempts: entry.attempts + 1,
lastError: errMsg,
}])) Defensive patterns
Strategy: retry
Validate before calling
if (!entry.cloudChatId) return // reconcile must mint a cloudChatId before a send is possible
Try / catch
catch (err) {
const errMsg = errorMessageFrom(err) ?? 'unknown'
console.warn('[chat-sync] sendMessages failed for', sessionId, errMsg)
await enqueuePersist(() => chatSessionsRepo.updateOutboxEntries(userId, [{
messageId: entry.messageId,
attempts: entry.attempts + 1, // increment, do not reset to 1
lastError: errMsg,
}]))
} Prevention
- Increment attempts instead of resetting so chronic failures become visible to eviction logic
- Drain the outbox after every reconnect, not only on new sends
- Surface outbox pending counts in the UI so users notice stalled messages
- Classify lastError (auth vs not-found vs validation) before retrying blindly
When it happens
Trigger: Flushing an outbox entry while the connection dropped; server rejecting the message (unknown chatId after the chat was deleted remotely, payload validation failure); auth expiry mid-drain.
Common situations: Sending messages offline (they queue in the outbox); resuming online with a backlog; server restarted and lost in-memory chat state.
Related errors
- [chat-sync] outbox drain failed for
- Retry target has no retriable source message
- Retry target has no retriable user message
- [context-bridge] Failed to refresh completed remote stream:
- [chat-ws] socket error:
AI-assisted analysis of moeru-ai/airi@b6d0809ecb (2026-08-18).
Data as JSON: /api/errors/5e7cd81320089286.
Report an issue: GitHub.