fix(sessions): run the assigned recovery pass in the all-profiles view

`?all_profiles=1` passed `project_assigned_limit=False`, on the premise that
its `visible_session_limit=None` first pass already returns every conversation.
It does not: `_load_cli_sessions_uncached` maps `visible_session_limit is None`
to CLI_VISIBLE_SESSION_LIMIT for the interactive pass, so that view truncates
by recency exactly like the single-profile one. Only cron/webhook/kanban limits
reach the reader as `limit=` directly, where None does mean unbounded.

Net effect of the old value: in the "All profiles" sidebar a project-assigned
CLI session older than the newest 20 interactive conversations was missing from
/api/sessions while its project chip still claimed it — the #6659 bug itself,
in the one view that shows every profile at once. Recovery pass 2 (the
unassigned refill) still ran there, so the two passes disagreed as well.

Pass PROJECT_ASSIGNED_CLI_LIMIT instead of None so the per-project bound and
the PROJECT_ASSIGNED_CLI_SCAN_CEILING guard apply per profile, rather than
issuing an unbounded assigned scan for every profile on the box.
This commit is contained in:
Rodrigo Gomes da Silva committed 2026-09-24 11:03:46 -03:00
1 parent 7c984f7ab6
commit bc1ee9d400
2 files changed
+52 -4

No files matched your search

@@ -738,3 +738,46 @@ def test_kanban_second_pass_still_projects_rows(fake_hermes_home, tmp_path):
assert "kanban-old" in by_id
# Upstream semantics: kanban rows get no chip from this helper.
assert by_id["kanban-old"]["project_id"] is None
# ── The all-profiles sidebar view runs the same recovery passes ───────────────
def test_all_profiles_view_recovers_project_assigned_sessions(
fake_hermes_home, tmp_path, monkeypatch
):
"""``?all_profiles=1`` truncates by recency too, so it needs both passes.
That path passes ``visible_session_limit=None``, which reads like
"unbounded" but is mapped to ``CLI_VISIBLE_SESSION_LIMIT`` for the
interactive pass — only the cron/webhook/kanban limits reach the reader as
``limit=`` directly, where None really does mean unbounded. Skipping the
assigned recovery pass here therefore lost exactly the sessions this
regression is about, in the one view that shows every profile at once.
"""
monkeypatch.setattr(models, "CLI_VISIBLE_SESSION_LIMIT", 5)
_register_projects(tmp_path, "project-123")
db_path = fake_hermes_home / "state.db"
rows = [
_session(f"recent-{index:02d}", BASE_TS + 100 + index)
for index in range(25)
]
rows.append(_session("older-assigned", BASE_TS, project_id="project-123"))
_write_state_db(db_path, rows)
# Enumerating real profiles shells out to the agent CLI; pin the one context.
monkeypatch.setattr(
models,
"_all_profiles_cli_contexts",
lambda: ([(fake_hermes_home, db_path, "default")], (("home", "default", 1),)),
)
sessions = models.get_cli_sessions(all_profiles=True)
by_id = {session["session_id"]: session for session in sessions}
assert "older-assigned" in by_id
assert by_id["older-assigned"]["project_id"] == "project-123"
assert [s["session_id"] for s in sessions].count("older-assigned") == 1
# The unassigned window is still bounded — the recovery pass is additive.
assert sum(1 for s in sessions if s["project_id"] is None) == 5