moeru-ai/airi · warning
[chat-sync] outbox drain failed for
Error message
[chat-sync] outbox drain failed for
What it means
The batched outbox drain failed to deliver all entries of one session in a single sendMessages call. Every entry in that failing session batch gets attempts + 1 and the shared lastError, while other sessions that succeeded are dequeued via dequeueOutbox(succeededIds). One poison message fails its whole session batch; the drain is single-flight guarded and safe to retrigger.
Source
Thrown at packages/stage-ui/src/stores/chat/session-store.ts:1224
for (const [sessionId, sessionEntries] of bySession) {
const meta = sessionMetas.value[sessionId]
const cloudChatId = meta?.cloudChatId
if (!cloudChatId)
continue
if (!wsClient || wsClient.status() !== 'open')
break
sessionEntries.sort((a, b) => a.queuedAt - b.queuedAt)
try {
await wsClient.sendMessages({
chatId: cloudChatId,
messages: sessionEntries.map(e => ({ id: e.messageId, role: e.role, content: e.content })),
})
succeededIds.push(...sessionEntries.map(e => e.messageId))
}
catch (err) {
const errMsg = errorMessageFrom(err) ?? 'unknown'
console.warn('[chat-sync] outbox drain failed for', sessionId, errMsg)
for (const entry of sessionEntries) {
failedUpdates.push({
messageId: entry.messageId,
attempts: entry.attempts + 1,
lastError: errMsg,
})
}
}
}
if (succeededIds.length > 0)
await enqueuePersist(() => chatSessionsRepo.dequeueOutbox(userId, succeededIds))
if (failedUpdates.length > 0)
await enqueuePersist(() => chatSessionsRepo.updateOutboxEntries(userId, failedUpdates))
await refreshOutboxPendingCount()
})().finally(() => {
outboxDrainTask = undefined
})View on GitHub (pinned to b6d0809ecb)
Solutions
- Restore connectivity and let the single-flight drain retry on its next trigger
- Find the poison message: entries with growing attempts/lastError in the outbox repo identify it
- If one message always fails its session batch, fix or remove it (e.g. re-mint the chatId) to unblock the rest
- When the whole batch keeps failing with not-found, verify the chat still exists server-side
Defensive patterns
Strategy: retry
Validate before calling
if (sessionEntries.length === 0) continue // nothing to send for this session if (!cloudChatId) continue // no mapping yet
Try / catch
try {
await wsClient.sendMessages({ chatId: cloudChatId, messages: sessionEntries.map(e => ({ id: e.messageId, role: e.role, content: e.content })) })
succeededIds.push(...sessionEntries.map(e => e.messageId))
}
catch (err) {
// fail only this session's batch; other sessions continue draining
for (const entry of sessionEntries)
failedUpdates.push({ messageId: entry.messageId, attempts: entry.attempts + 1, lastError: errorMessageFrom(err) ?? 'unknown' })
} Prevention
- Batch per session so one bad session never blocks others (source already does this)
- Cap attempts and dead-letter entries that always fail to unblock the rest
- Inspect per-entry lastError to distinguish server rejections from network drops
When it happens
Trigger: Batch drain over a flaky connection; one malformed or oversized message in a session batch failing the entire sendMessages call; chat deleted remotely before the backlog drained.
Common situations: Startup drain of an offline backlog; resuming after a long sleep; a single invalid message repeatedly blocking its session's batch.
Related errors
- [chat-sync] sendMessages 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/aed382d04d96bffb.
Report an issue: GitHub.