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 tab

View on GitHub (pinned to b6d0809ecb)

Solutions

  1. Read the message logged after the cloudChatId - it carries the underlying network or HTTP error
  2. Do nothing for transient failures: the tombstone stays queued and the drain re-runs on the next sync
  3. Re-authenticate if the message shows 401/403, then trigger another sync
  4. 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
  5. 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

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


AI-assisted analysis of moeru-ai/airi@b6d0809ecb (2026-08-18). Data as JSON: /api/errors/040c3049c5daa05c. Report an issue: GitHub.