fix(session): guard lossy migration by stored key

This commit is contained in:
Xubin Ren
2026-06-27 16:52:54 +08:00
parent 3ce77633c0
commit c90e433057
2 changed files with 52 additions and 0 deletions
+27
View File
@@ -433,6 +433,24 @@ class SessionManager:
"""Legacy global session path (~/.nanobot/sessions/).""" """Legacy global session path (~/.nanobot/sessions/)."""
return self.legacy_sessions_dir / f"{self.safe_key(key)}.jsonl" return self.legacy_sessions_dir / f"{self.safe_key(key)}.jsonl"
@staticmethod
def _stored_key_for_path(path: Path) -> str | None:
"""Read the stored session key from a JSONL metadata row, if present."""
try:
with open(path, encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line:
continue
data = json.loads(line)
if data.get("_type") == "metadata":
stored_key = data.get("key")
return stored_key if isinstance(stored_key, str) else None
return None
except Exception:
return None
return None
def get_or_create(self, key: str) -> Session: def get_or_create(self, key: str) -> Session:
""" """
Get an existing session or create a new one. Get an existing session or create a new one.
@@ -464,6 +482,15 @@ class SessionManager:
for fallback_path, description in fallback_paths: for fallback_path, description in fallback_paths:
if not fallback_path.exists(): if not fallback_path.exists():
continue continue
stored_key = self._stored_key_for_path(fallback_path)
if stored_key and stored_key != key:
logger.info(
"Skipping migration for {} from {} because it belongs to {}",
key,
description,
stored_key,
)
continue
try: try:
shutil.move(str(fallback_path), str(path)) shutil.move(str(fallback_path), str(path))
logger.info("Migrated session {} from {}", key, description) logger.info("Migrated session {} from {}", key, description)
+25
View File
@@ -93,6 +93,31 @@ def test_load_migrates_lossy_to_new_path(tmp_path: Path, monkeypatch) -> None:
assert not lossy_path.exists() assert not lossy_path.exists()
def test_load_does_not_migrate_lossy_path_for_different_stored_key(
tmp_path: Path,
monkeypatch,
) -> None:
sm = _manager(tmp_path, monkeypatch)
first_key = "telegram:a_b"
second_key = "telegram:a:b"
lossy_path = sm._get_legacy_lossy_path(first_key)
assert lossy_path == sm._get_legacy_lossy_path(second_key)
_write_session_file(lossy_path, first_key, "belongs to first")
loaded_second = sm._load(second_key)
assert loaded_second is None
assert lossy_path.exists()
assert not sm._get_session_path(second_key).exists()
loaded_first = sm._load(first_key)
assert loaded_first is not None
assert loaded_first.messages[0]["content"] == "belongs to first"
assert sm._get_session_path(first_key).exists()
assert not lossy_path.exists()
def test_safe_key_is_lossy() -> None: def test_safe_key_is_lossy() -> None:
assert SessionManager.safe_key("telegram:a_b") == SessionManager.safe_key("telegram:a:b") assert SessionManager.safe_key("telegram:a_b") == SessionManager.safe_key("telegram:a:b")