From c17ed6371e9e1cc7eee0690f7825e4c69ec51296 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Sat, 4 Jul 2026 00:42:07 -0400 Subject: [PATCH] fix(#5498): preserve messaging sessions outside WebUI tombstones --- api/routes.py | 2 +- tests/test_issue2057_worktree_lifecycle.py | 78 +++++++++++++++++++++- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/api/routes.py b/api/routes.py index 303baecc..aefee8e5 100644 --- a/api/routes.py +++ b/api/routes.py @@ -13622,7 +13622,7 @@ def handle_post(handler, parsed) -> bool: prune_session_from_index(sid) except Exception: logger.debug("Failed to prune deleted session from index: %s", sid, exc_info=True) - if sidecar_deleted: + if sidecar_deleted and not is_messaging_session: try: _record_webui_deleted_session_tombstone(sid) except Exception: diff --git a/tests/test_issue2057_worktree_lifecycle.py b/tests/test_issue2057_worktree_lifecycle.py index 6f86f3be..ef718eea 100644 --- a/tests/test_issue2057_worktree_lifecycle.py +++ b/tests/test_issue2057_worktree_lifecycle.py @@ -1,5 +1,6 @@ -from types import SimpleNamespace +import sqlite3 from pathlib import Path +from types import SimpleNamespace import api.models as models import api.routes as routes @@ -49,6 +50,45 @@ def _worktree_session(tmp_path, session_id): return s, worktree +def _make_state_db(path, sid, *, source="telegram"): + conn = sqlite3.connect(str(path)) + conn.executescript( + """ + CREATE TABLE sessions ( + id TEXT PRIMARY KEY, + source TEXT, + model TEXT, + message_count INTEGER DEFAULT 0, + started_at REAL, + title TEXT, + cwd TEXT + ); + CREATE TABLE messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + session_id TEXT, + role TEXT, + content TEXT, + timestamp REAL + ); + """ + ) + conn.execute( + "INSERT INTO sessions (id, source, model, message_count, started_at, title, cwd) " + "VALUES (?, ?, 'MiniMax-M3', 2, 1781024055.0, 'Telegram chat', ?)", + (sid, source, str(path.parent)), + ) + conn.execute( + "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, 'user', 'hi', 1781024055.0)", + (sid,), + ) + conn.execute( + "INSERT INTO messages (session_id, role, content, timestamp) VALUES (?, 'assistant', 'hello', 1781024056.0)", + (sid,), + ) + conn.commit() + conn.close() + + def test_delete_worktree_session_reports_retained_worktree_without_cleanup(tmp_path, monkeypatch): session_dir = _isolate_session_store(tmp_path, monkeypatch) session, worktree = _worktree_session(tmp_path, "wtdelete1") @@ -103,6 +143,42 @@ def test_delete_session_records_tombstone_when_state_db_delete_fails(tmp_path, m assert sid in models._load_webui_deleted_session_tombstone() +def test_delete_messaging_session_reopens_read_only_without_deleted_webui_tombstone( + tmp_path, monkeypatch +): + session_dir = _isolate_session_store(tmp_path, monkeypatch) + sid = "telegramdelete1" + state_db = tmp_path / "state.db" + _make_state_db(state_db, sid) + monkeypatch.setattr(models, "_active_state_db_path", lambda: state_db) + session = Session(session_id=sid, title="Telegram chat") + session.save() + captured = _capture_post(monkeypatch, {"session_id": sid}) + cli_meta = { + "session_id": sid, + "source_tag": "telegram", + "raw_source": "telegram", + "session_source": "messaging", + } + monkeypatch.setattr(routes, "_lookup_cli_session_metadata", lambda value: cli_meta) + monkeypatch.setattr(routes, "_is_messaging_session_id", lambda value: True) + delete_calls = [] + monkeypatch.setattr(models, "delete_cli_session", lambda value: delete_calls.append(value)) + + assert routes.handle_post(object(), SimpleNamespace(path="/api/session/delete")) is True + sess, reason = routes._claim_or_synthesize_cli_session(sid) + + assert captured["status"] == 200 + assert captured["payload"]["ok"] is True + assert not (session_dir / f"{sid}.json").exists() + assert sid not in models._load_webui_deleted_session_tombstone() + assert delete_calls == [] + assert reason == "not_claimable" + assert sess is not None + assert sess.read_only is True + assert sess.session_source == "messaging" + + def test_archive_worktree_session_reports_retained_worktree_without_cleanup(tmp_path, monkeypatch): _isolate_session_store(tmp_path, monkeypatch) session, worktree = _worktree_session(tmp_path, "wtarchive1")