fix(sessions): bound the merged assigned CLI set by a shared ceiling

The route cap bounded project-assigned CLI rows PER PROJECT at 200, which
is not a bound on the payload: 200 rows times N projects grows with the
project count. The review's own reproduction still held — 1,000 assigned
conversations spread over 5 projects returned all 1,000, because no
project exceeded its own 200 (#6659 review finding 1). Imported WebUI
sidecars from all_sessions() never pass through a state.db cap, so this
merged pass is the only place that can bound them.

Give each project an EQUAL share of a global CLI_PROJECT_ASSIGNED_SCAN_CEILING
(ceiling // projects, never below 1) once enough projects exist to make that
share tighter than the per-project cap. Same shape as the model-side share of
PROJECT_ASSIGNED_CLI_SCAN_CEILING, and for the same reason a flat global cap
is unusable here: it would leave every project past it permanently
unreachable on each rebuild (greptile P1). Profiles with a few projects keep
the full 200, so the common case is unchanged.

The ceiling is duplicated as a literal because api.models is imported below
this cap in api/routes.py; a test pins the two equal, mirroring
test_background_source_exclusion_literal_matches_the_constant.
This commit is contained in:
Rodrigo Gomes da Silva committed 2026-09-24 11:03:46 -03:00
1 parent b5ccf56c53
commit 76be369c54
2 files changed
+115 -1

No files matched your search

+34 -1
View File
@@ -10494,12 +10494,23 @@ def _cli_visible_session_cap() -> int:
# which no model-side cap applies to — so it is the only place that can actually
# bound the assigned set (#6659 review finding 1).
CLI_PROJECT_ASSIGNED_CAP = 200
# Global ceiling on the assigned rows this cap will emit, mirroring
# models.PROJECT_ASSIGNED_CLI_SCAN_CEILING so the route can never hand back more
# assigned conversations than the model pass was allowed to recover. A flat
# per-project bound does NOT bound the payload: 200 rows x N projects grows with
# the project count, and 1,000 assigned rows spread over 5 projects still
# returned all 1,000 — the exact reproduction from #6659 review finding 1.
# Spelled as a literal because api.models is imported further down this module;
# pinned equal to the model constant by
# test_route_project_ceiling_matches_the_model_constant.
CLI_PROJECT_ASSIGNED_SCAN_CEILING = 2000
def _cap_recent_cli_sessions(
sessions: list[dict],
cli_cap: int | None = None,
project_cap: int = CLI_PROJECT_ASSIGNED_CAP,
project_ceiling: int = CLI_PROJECT_ASSIGNED_SCAN_CEILING,
) -> list[dict]:
"""Cap the default CLI list while retaining project-addressable rows.
@@ -10519,11 +10530,33 @@ def _cap_recent_cli_sessions(
window is full. Past that bound they are dropped: keeping assigned rows past
the *recent* cap is the fix, keeping them past *all* bounds just trades a
vanishing session for a stalled sidebar.
That per-project budget is an EQUAL share of ``project_ceiling``
(``ceiling // projects``, never below 1) once enough projects exist to exceed
it, which is what makes the merged payload bounded no matter how many
projects a profile has while keeping a project's allowance independent of how
loud its neighbours are. Same shape as the model-side share of
``PROJECT_ASSIGNED_CLI_SCAN_CEILING``, and for the same reason a flat global
cap is not usable here: it would leave every project past it permanently
unreachable on every rebuild (greptile P1 on #6659).
"""
if cli_cap is None:
cli_cap = _cli_visible_session_cap()
if cli_cap <= 0:
return sessions
# The share denominator has to be known before the first assigned row is
# charged against it, so count the distinct assigned projects up front.
assigned_projects = {
project_id
for session in sessions
if _is_cli_session_for_settings(session)
and (project_id := str(session.get("project_id") or "").strip())
}
effective_project_cap = project_cap
if project_cap > 0 and project_ceiling > 0 and assigned_projects:
effective_project_cap = min(
project_cap, max(1, project_ceiling // len(assigned_projects))
)
kept = []
recent_seen = 0
unassigned_seen = 0
@@ -10539,7 +10572,7 @@ def _cap_recent_cli_sessions(
else:
seen = assigned_seen.get(project_id, 0) + 1
assigned_seen[project_id] = seen
if project_cap > 0 and seen > project_cap:
if effective_project_cap > 0 and seen > effective_project_cap:
continue
if recent_seen >= cli_cap:
session = dict(session)