#2671·airi

bug(stage-ui): deleted chat messages come back after a switch of the character card

Author: chiba233Created Sep 27, 2026Updated Sep 28, 2026
Labelsbug

Describe the bug

When cloud sync is on, a deleted chat message comes back after I switch the character card. As a result, delete looks broken until I restart the app.

Steps to reproduce

  1. Sign in, and open the chat in the desktop app.
  2. Send some messages.
  3. Delete one of these messages. The message disappears.
  4. Switch to a character card that has no chat session yet.
  5. Switch back to the first card. The deleted message is back, at the end of the history.

I reproduced this on main at c4da51035.

Actual behavior and impact

  • A deleted message comes back, and the restored copy is saved to IndexedDB.
  • The restored message moves to the end of the history.
  • The console also shows [chat-sync] pullMessages failed for <sessionId> undefined. This warning comes from the same catch-up pull.

Expected behavior

A deleted message stays deleted after a switch of the character card, and after the next cloud sync.

Code investigation

Inspected at c4da510353b6b7ce96beb1558888f7d5e0e2127a.

I ran the session store against a fake chat server. The fake server does not echo sendMessages back to the sender, like the real server. The result: I sent u1, u2 and u3, and deleted u1. After a switch to a new card, the local history was [u2, u3, u1]. The catch-up pull used afterSeq: 0 and returned all three messages.

Three behaviors together cause this:

  1. deleteMessage changes only local state. The server keeps the message.
  2. The sender's cloudMaxSeq cursor does not move after a send. pushMessageToCloud does not update it. The server does not push the new messages to the sending connection.
  3. A switch to a card without a session creates a session. createSession starts reconcileCloudSessions, and this pulls each mapped session from its old cursor. mergeCloudMessagesIntoLocal treats the deleted id as new and appends it.

After a restart, delete appears to work because the catch-up pull already moved the cursor to the latest seq. The restored messages stay.

Related: the warning prints undefined

The same catch-up pull also pulls sessions whose remote chat is gone, because reconcileLocalAndRemote keeps their mapping. The server throws an ApiError. eventa 1.0.1 sends it through JSON.stringify, and Error.message is not enumerable. The client receives {"error":{"statusCode":404,"errorCode":"NOT_FOUND","name":"ApiError"}}, so errorMessageFrom returns undefined.

System Info

bash
System:
    OS: macOS 27.2
    CPU: (10) arm64 Apple M1 Pro
    Memory: 491.19 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 24.15.0 - /Users/yumeka/.nvm/versions/node/v24.15.0/bin/node
    npm: 11.12.1 - /Users/yumeka/.nvm/versions/node/v24.15.0/bin/npm
    pnpm: 11.13.0 - /opt/homebrew/bin/pnpm
    bun: 1.3.14 - /Users/yumeka/.bun/bin/bun
  Browsers:
    Chrome: 153.0.8010.53
    Firefox: 150.0.3
    Safari: 27.2

Validations

  • Follow our Code of Conduct
  • Read the Contributing Guide.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
  • Check that this is a concrete bug. For Q&A, please open a GitHub Discussion instead.

Contributions

  • I am willing to submit a PR to fix this issue
  • I am willing to submit a PR with failing tests (actually just go ahead and do it, thanks!)