From cfeccbdf34224415b43a50b2988fd149a4f6b10b Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 11 Sep 2026 02:07:13 -0700 Subject: [PATCH] fix(hosted-rooms): local_profiles skips the profile-delete tombstone dir `hermes profile delete` leaves `profiles/.deleted/` behind. `HostedRoomService.local_profiles()` fed every subdirectory name to the roster, so `.deleted` failed `validate_roster`'s identifier check and `plan_next_task` raised on every cycle for every room until the directory was removed by hand (#106847, bug 2). Skip dot-dirs and tombstoned profiles, using the same `named_profile_is_deleted` predicate `hermes_cli.profiles` uses to list live profiles. Refs #106847 --- tests/tui_gateway/test_hosted_room_service.py | 16 ++++++++++++++++ tui_gateway/hosted_room_service.py | 8 +++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/tests/tui_gateway/test_hosted_room_service.py b/tests/tui_gateway/test_hosted_room_service.py index 0d1e3b731d..80596b14a0 100644 --- a/tests/tui_gateway/test_hosted_room_service.py +++ b/tests/tui_gateway/test_hosted_room_service.py @@ -2073,3 +2073,19 @@ def test_peer_recovery_replays_the_same_execution_generation(tmp_path: Path): assert recovered["task_id"] == "task-1" assert recovered["execution_generation"] == 1 assert recovered["prompt"] == "Recover the accepted review." + + +def test_local_profiles_skips_delete_tombstones_and_dot_dirs(tmp_path: Path): + """`hermes profile delete` leaves ``profiles/.deleted/``; neither the tombstone dir nor a + tombstoned profile is a roster member (#106847: ``.deleted`` failed validate_roster every cycle).""" + from hermes_constants import mark_named_profile_deleted + + profiles = tmp_path / "profiles" + (profiles / "ops").mkdir(parents=True) + (profiles / "gone").mkdir() + mark_named_profile_deleted(profiles / "gone") + assert (profiles / ".deleted").is_dir() + + service = HostedRoomService(_server(), db_path=tmp_path / "shared-state.db") + + assert service.local_profiles() == ("default", "ops") diff --git a/tui_gateway/hosted_room_service.py b/tui_gateway/hosted_room_service.py index ca23d955ed..a5d7e131a8 100644 --- a/tui_gateway/hosted_room_service.py +++ b/tui_gateway/hosted_room_service.py @@ -124,9 +124,15 @@ class HostedRoomService: return self.db_path.parent def local_profiles(self) -> tuple[str, ...]: + from hermes_constants import named_profile_is_deleted + profiles, profiles_dir = {"default"}, self.root / "profiles" if profiles_dir.is_dir(): - profiles.update(path.name for path in profiles_dir.iterdir() if path.is_dir()) + # ``profiles/.deleted/`` is the tombstone dir `hermes profile delete` leaves behind, not a + # profile: feeding it to validate_roster failed plan_next_task on every cycle (#106847). + profiles.update( + path.name for path in profiles_dir.iterdir() + if path.is_dir() and not path.name.startswith(".") and not named_profile_is_deleted(path)) return tuple(sorted(profiles)) def bindings(self) -> tuple[HostedRoomBinding, ...]: