moeru-ai/airi · warning
[chat-sync] tombstone drain failed for
Error message
[chat-sync] tombstone drain failed for
What it means
During chat sync, tombstones (locally deleted chats pending server-side deletion) are drained by calling mapper.deleteChat(cloudChatId) per ID. Any failure whose message does not contain 'HTTP 404' logs this warning, and the tombstone is kept, so deletion is retried on a later sync cycle. HTTP 404 is treated as success because the server already deleted the chat (idempotent delete).
Source
Thrown at packages/stage-ui/src/stores/chat/session-store.ts:1278
const tombstones = await chatSessionsRepo.getTombstones(userId)
if (tombstones.length === 0)
return
const mapper = getCloudMapper()
const succeeded: string[] = []
for (const cloudChatId of tombstones) {
try {
await mapper.deleteChat(cloudChatId)
succeeded.push(cloudChatId)
}
catch (err) {
const msg = errorMessageFrom(err) ?? ''
// 404 = server already cleared it, treat as success so the
// tombstone gets dropped instead of retried forever.
if (msg.includes('HTTP 404'))
succeeded.push(cloudChatId)
else
console.warn('[chat-sync] tombstone drain failed for', cloudChatId, msg)
}
}
if (succeeded.length > 0)
await enqueuePersist(() => chatSessionsRepo.removeTombstones(userId, succeeded))
}
async function initialize() {
if (ready.value) {
return
}
if (initializePromise) {
return initializePromise
}
initializing.value = true
initializePromise = (async () => {
await ensureActiveSessionForCharacter()
ready.value = true
// Surface any outbox left over from a previous session (closed tabView on GitHub (pinned to b6d0809ecb)
Solutions
- Read the message logged after the cloudChatId - it carries the underlying network or HTTP error
- Do nothing for transient failures: the tombstone stays queued and the drain re-runs on the next sync
- Re-authenticate if the message shows 401/403, then trigger another sync
- If the server genuinely lacks the chat but returns something other than 404, fix the server's delete endpoint contract to return 404 for missing resources
- Correlate the logged ID with the failing DELETE request in the DevTools network tab
Defensive patterns
Strategy: try-catch
Try / catch
for (const id of tombstones) {
try {
await mapper.deleteChat(id)
succeeded.push(id)
}
catch (err) {
// 404 means the server already deleted it: idempotent success
if (errorMessageFrom(err)?.includes('HTTP 404'))
succeeded.push(id)
else
keepForNextSync(id) // leave the tombstone so deletion retries
}
} Prevention
- Keep sessions authenticated before triggering syncs
- Treat 404 on delete as success so tombstones cannot wedge the queue
- Never clear tombstones on unknown errors - that silently loses deletions
- Log the cloudChatId alongside the error for request correlation
When it happens
Trigger: The delete API call fails with 5xx, network errors, expired auth (401/403), or validation rejections (409/422) - anything except HTTP 404. The caught error message is printed after the chat ID.
Common situations: Flaky connection during background sync; session token expired mid-drain; server restart while deletes are in flight; self-hosted API version where the delete endpoint moved or returns a non-404 for missing chats.
Related errors
- [chat-sync] DELETE /api/v1/chats failed for
- [chat-ws] socket error:
- [chat-ws] ws error event:
- [chat-sync] pullMessages failed for
- [chat-sync] listChats failed; skipping reconcile this round:
AI-assisted analysis of moeru-ai/airi@b6d0809ecb (2026-08-18).
Data as JSON: /api/errors/040c3049c5daa05c.
Report an issue: GitHub.