{"record":{"id":"a937412121453822","repo":"moeru-ai/airi","slug":"chat-sync-listchats-failed-skipping-reconcile-t","errorCode":null,"errorMessage":"[chat-sync] listChats failed; skipping reconcile this round:","messagePattern":"\\[chat-sync\\] listChats failed; skipping reconcile this round:","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-ui/src/stores/chat/session-store.ts","lineNumber":813,"sourceCode":"    const myEpoch = reconcileEpoch\n    const isStaleEpoch = () => myEpoch !== reconcileEpoch\n\n    const reconcileTask = (async () => {\n      const currentUserId = getCurrentUserId()\n      if (currentUserId === 'local') {\n        console.info('[chat-sync] reconcile skipped: anonymous user')\n        return\n      }\n\n      console.info('[chat-sync] reconcile start', { userId: currentUserId, serverUrl: SERVER_URL })\n      const mapper = getCloudMapper()\n\n      let remoteChats\n      try {\n        remoteChats = await mapper.listChats()\n      }\n      catch (err) {\n        console.warn('[chat-sync] listChats failed; skipping reconcile this round:', errorMessageFrom(err))\n        return\n      }\n      if (isStaleEpoch())\n        return\n      console.info('[chat-sync] listChats →', remoteChats.length, 'remote chats')\n\n      // Snapshot local metas owned by this user. Anonymous-era sessions are\n      // not promoted to the cloud automatically — the user can re-open them\n      // after signing in and the server is unaware of them.\n      const localOwnedMetas = Object.values(sessionMetas.value).filter(meta => meta.userId === currentUserId)\n      const plan = reconcileLocalAndRemote(localOwnedMetas, remoteChats)\n\n      // Tombstones: drop adopt entries for chats the user already deleted.\n      // The server's soft-delete may not have committed yet (offline DELETE\n      // path), so we still need to remember \"do not re-adopt this id\".\n      const tombstones = await chatSessionsRepo.getTombstones(currentUserId)\n      if (isStaleEpoch())\n        return","sourceCodeStart":795,"sourceCodeEnd":831,"githubUrl":"https://github.com/moeru-ai/airi/blob/b6d0809ecb6421d492c4ce38aa7a76146c83269d/packages/stage-ui/src/stores/chat/session-store.ts#L795-L831","documentation":"Reconcile cannot start this round: mapper.listChats() rejected, so the whole local-vs-remote reconciliation is skipped via early return. Local sessions do not get cloud ids minted, remote chats are not adopted, and tombstones are not drained until the next round. Reconcile re-runs after every successful (re)connect, so divergence is temporary once connectivity recovers.","triggerScenarios":"Reconcile round starting while the backend is unreachable; auth token expired (401 on GET /api/v1/chats); proxy/CORS misconfiguration blocking the list call; API app down or returning 5xx.","commonSituations":"Backend down for maintenance while the client keeps scheduling reconcile rounds; token refresh missing so 401 repeats; SERVER_URL pointing at an old deployment.","solutions":["Check that the hosted backend stack (server/docker-compose.yaml) is running and SERVER_URL is correct","Re-authenticate if the underlying status is 401/403","Do nothing for transient outages — reconcile re-runs after every successful reconnect","Inspect the network request for GET /api/v1/chats to confirm status code and CORS/proxy behavior","Rely on the outbox to keep local changes queueing so a skipped round never loses data"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"if (!currentUserId) return // source already skips reconcile for anonymous users","typeGuard":null,"tryCatchPattern":"let remoteChats\ntry {\n  remoteChats = await mapper.listChats()\n}\ncatch (err) {\n  console.warn('[chat-sync] listChats failed; skipping reconcile this round:', errorMessageFrom(err))\n  return // next successful (re)connect re-runs reconcile\n}\nif (isStaleEpoch()) return","preventionTips":["Health-check SERVER_URL at startup and show connection state in settings","Refresh auth before reconcile when tokens are short-lived","Keep reconcile idempotent so skipped rounds are harmless","Rely on the outbox for local writes so skipped rounds never lose data"],"tags":["chat-sync","http-get","reconcile","network"],"backgroundTag":"http-request-failed","analyzedSha":"b6d0809ecb6421d492c4ce38aa7a76146c83269d","analyzedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}