moeru-ai/airi · warning

[chat-sync] pullMessages failed for

Error message

[chat-sync] pullMessages failed for

What it means

Warns when wsClient.pullMessages({ chatId, afterSeq }) rejects while lazily hydrating an adopted remote chat on first loadSession. mergeCloudMessagesIntoSession is skipped and the session is NOT added to cloudHydratedSessions, so opening the session again retries the pull. The session still opens, but shows only its empty shell until a pull succeeds.

Source

Thrown at packages/stage-ui/src/stores/chat/session-store.ts:767

    if (!wsClient || wsClient.status() !== 'open')
      return
    const meta = sessionMetas.value[sessionId]
    if (!meta?.cloudChatId)
      return

    try {
      const result = await wsClient.pullMessages({
        chatId: meta.cloudChatId,
        afterSeq: meta.cloudMaxSeq ?? 0,
      })
      mergeCloudMessagesIntoSession(sessionId, {
        messages: result.messages,
        toSeq: result.seq,
      })
      cloudHydratedSessions.add(sessionId)
    }
    catch (err) {
      console.warn('[chat-sync] pullMessages failed for', sessionId, errorMessageFrom(err))
    }
  }

  /**
   * Reconcile local sessions against the server `chats` table. Called after
   * the local index loads and after every successful (re)connect.
   *
   * - Local sessions without a `cloudChatId` either claim a remote chat with
   *   the same id or trigger `POST /api/v1/chats` to mint one.
   * - Remote chats that have no local mapping are adopted as empty-shell
   *   sessions; their messages are pulled lazily on first `loadSession`.
   * - Remote chats whose id is in the user's tombstone set are skipped — the
   *   user already deleted them locally and the server-side soft-delete may
   *   not have committed yet.
   *
   * Reentrant: a single in-flight task is shared across concurrent callers.
   * If a new "open" event fires while a reconcile is running, a follow-up
   * pass is scheduled in `finally` so catch-up pulls do not get lost.

View on GitHub (pinned to b6d0809ecb)

Solutions

  1. Confirm the sync client is connected before opening remote-only sessions (reconnect re-runs hydration)
  2. Close and reopen the session — cloudHydratedSessions was not marked, so loadSession retries the pull
  3. If the chat was deleted remotely, run a reconcile (listChats) so the dead local mapping is cleaned up instead of retrying a dead chatId
  4. For persistent failures, inspect server logs for pullMessages errors on that chatId and fix the auth/network cause
Defensive patterns

Strategy: retry

Validate before calling

if (!meta.cloudChatId) return // pull only applies to cloud-mapped sessions
if (cloudHydratedSessions.has(sessionId)) return // already hydrated

Try / catch

try {
  const result = await wsClient.pullMessages({ chatId: meta.cloudChatId, afterSeq: meta.cloudMaxSeq ?? 0 })
  mergeCloudMessagesIntoSession(sessionId, { messages: result.messages, toSeq: result.seq })
  cloudHydratedSessions.add(sessionId)
}
catch (err) {
  // do NOT add to cloudHydratedSessions — the next loadSession retries the pull
  console.warn('[chat-sync] pullMessages failed for', sessionId, errorMessageFrom(err))
}

Prevention

When it happens

Trigger: loadSession racing a disconnect (pull issued while the WebSocket RPC is down); the chat was deleted on the server so chatId is unknown; auth token expired mid-session; server 5xx during the hydration pull.

Common situations: Laptop resumes from sleep and the user opens an adopted chat before reconnect finishes; the chat was deleted from another device moments earlier; flaky network on first open of a remote-only session.

Related errors


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