From 274613f064da2d008e30691b2c68cb4ee6df163b Mon Sep 17 00:00:00 2001 From: KDB <937925477@qq.com> Date: Thu, 23 Jul 2026 23:07:15 +0800 Subject: [PATCH] fix(session): tolerate files removed during listing --- nanobot/session/manager.py | 2 ++ tests/agent/test_auto_compact.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index bb78e65f..a7919674 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -986,6 +986,8 @@ class SessionManager: "path": str(path), } ) + except FileNotFoundError: + continue except _SESSION_DATA_ERRORS: repaired = self._repair(fallback_key, path=path) if repaired is not None: diff --git a/tests/agent/test_auto_compact.py b/tests/agent/test_auto_compact.py index 35d96186..ce0f2205 100644 --- a/tests/agent/test_auto_compact.py +++ b/tests/agent/test_auto_compact.py @@ -781,6 +781,40 @@ class TestProactiveAutoCompact: ) await _drain_background_tasks(loop) + @pytest.mark.asyncio + async def test_idle_tick_survives_session_removed_during_listing( + self, + tmp_path, + monkeypatch, + ): + loop = _make_loop(tmp_path, session_ttl_minutes=15) + removed = loop.sessions.get_or_create("cli:removed") + removed.add_message("user", "remove me") + loop.sessions.save(removed) + healthy = loop.sessions.get_or_create("cli:healthy") + healthy.add_message("user", "keep me") + loop.sessions.save(healthy) + + removed_path = loop.sessions._get_session_path(removed.key) + original_open = open + + def remove_before_open(path, *args, **kwargs): + if Path(path) == removed_path: + removed_path.unlink() + return original_open(path, *args, **kwargs) + + monkeypatch.setattr("builtins.open", remove_before_open) + + async def idle_once(): + loop._running = False + raise asyncio.TimeoutError + + monkeypatch.setattr(loop.bus, "consume_inbound", idle_once) + + await loop.run() + + assert [row["key"] for row in loop.sessions.list_sessions()] == ["cli:healthy"] + @pytest.mark.asyncio async def test_no_check_when_ttl_disabled(self, tmp_path): """check_expired should be a no-op when TTL is 0."""