Skip to content

Commit 31e6a6e

Browse files
JSKittyclaude
andcommitted
fix(chat): drop orphaned pending-id event rows (ghost duplicates)
Mid-flight persists (upload progress, preview updates) could land a DB row under a message's optimistic pending id; the finalized message then saved under its REAL id, orphaning the pending-keyed row as a ghost duplicate that renders on reload. 92 such rows existed in a live long-running account. on_sent now deletes the pending-keyed row once the finalized id lands (session-guarded; the shared delete also retreats a read marker that pointed at it), and migration 64 sweeps existing orphans. Rows still flagged pending or failed are live send-state for the retry UI and stay untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 6330108 commit 31e6a6e

2 files changed

Lines changed: 28 additions & 0 deletions

File tree

crates/vector-core/src/db/schema.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,5 +749,22 @@ pub fn run_migrations(conn: &mut rusqlite::Connection) -> Result<(), String> {
749749
Ok(())
750750
})?;
751751

752+
// =========================================================================
753+
// Migration 64: Drop orphaned pending-id event rows
754+
// =========================================================================
755+
// Mid-flight persists could land a row under a message's optimistic
756+
// "pending-…" id; the finalized message then saved under its REAL id,
757+
// orphaning the pending-keyed row as a ghost duplicate that renders on
758+
// reload. Rows still flagged pending/failed are live send-state (the
759+
// retry UI needs them) and stay.
760+
run_atomic_migration(conn, 64, "Drop orphaned pending-id event rows", |tx| {
761+
tx.execute(
762+
"DELETE FROM events WHERE id LIKE 'pending-%' AND pending = 0 AND failed = 0",
763+
[],
764+
)
765+
.map_err(|e| format!("Failed to drop orphaned pending rows: {}", e))?;
766+
Ok(())
767+
})?;
768+
752769
Ok(())
753770
}

src-tauri/src/message/sending.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,17 @@ impl SendCallback for TauriSendCallback {
100100

101101
fn on_sent(&self, chat_id: &str, old_id: &str, msg: &Message) {
102102
UPLOAD_CANCEL_FLAGS.lock().unwrap().remove(old_id);
103+
// Mid-flight persists (upload progress, previews) can land a DB row
104+
// under the optimistic pending id; the finalized message saves under
105+
// its real id, orphaning that row as a ghost duplicate on reload.
106+
if old_id.starts_with("pending-") && old_id != msg.id {
107+
let pending_id = old_id.to_string();
108+
let session = vector_core::state::SessionGuard::capture();
109+
tokio::spawn(async move {
110+
if !session.is_valid() { return; }
111+
let _ = vector_core::db::events::delete_event(&pending_id);
112+
});
113+
}
103114
if let Some(handle) = TAURI_APP.get() {
104115
handle.emit("message_update", serde_json::json!({
105116
"old_id": old_id,

0 commit comments

Comments
 (0)