From 76838c087f7f743aa3ad58ebf23c69564a26e596 Mon Sep 17 00:00:00 2001 From: Mrinal Chanshetty Date: Sat, 24 Oct 2026 20:20:49 -0500 Subject: [PATCH] fix(proxy): honor exclude_team_ids on the team tags page The tags page took its team from team_ids alone, so a team that was both named and excluded still returned its tag spend, while the aggregated team report returned nothing for the same request. It now returns an empty page after the permission check. Also drops a comment that restated the code. --- .../management_endpoints/daily_activity_routes.py | 10 +++++----- litellm/repositories/daily_activity_sql.py | 1 - .../test_daily_activity_routes.py | 14 ++++++++++++++ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/management_endpoints/daily_activity_routes.py b/litellm/proxy/management_endpoints/daily_activity_routes.py index 13d12cbecee..7824540c54c 100644 --- a/litellm/proxy/management_endpoints/daily_activity_routes.py +++ b/litellm/proxy/management_endpoints/daily_activity_routes.py @@ -62,6 +62,7 @@ from litellm.types.repositories.daily_activity import ( ExportType, KeyMetadataRow, KeySpendRow, + TagPage, UserMetadataRow, UserSpendRow, ) @@ -423,11 +424,10 @@ def _register_tag_page_route(router: APIRouter, resolver: EntityScopeResolver, p prisma_client, user_aggregated=False, ) - page: Final = await repository.tag_page( - resolved.scope, - team_id=team_ids[0], - offset=offset, - limit=limit, + page: Final = ( + TagPage(rows=(), total_tags=0) + if team_ids[0] in resolved.scope.exclude_entity_ids + else await repository.tag_page(resolved.scope, team_id=team_ids[0], offset=offset, limit=limit) ) return DailyActivityTagPageResponse( tags=list(page.rows), diff --git a/litellm/repositories/daily_activity_sql.py b/litellm/repositories/daily_activity_sql.py index 366de5c68b2..7c3eb568971 100644 --- a/litellm/repositories/daily_activity_sql.py +++ b/litellm/repositories/daily_activity_sql.py @@ -106,7 +106,6 @@ def build_where_clause(scope: DailyActivityScope, *, start_index: int = 1) -> tu return " AND ".join(conditions), params -# Same rule as common_daily_activity._is_user_agent_tag _NOT_USER_AGENT_TAG: Final = "NOT (LOWER(BTRIM(tag)) LIKE 'user-agent:%' OR LOWER(BTRIM(tag)) LIKE 'user agent:%')" diff --git a/tests/unit/proxy/management_endpoints/test_daily_activity_routes.py b/tests/unit/proxy/management_endpoints/test_daily_activity_routes.py index 80cfd930607..0ca61a47da1 100644 --- a/tests/unit/proxy/management_endpoints/test_daily_activity_routes.py +++ b/tests/unit/proxy/management_endpoints/test_daily_activity_routes.py @@ -1375,6 +1375,20 @@ def test_team_tag_page_returns_404_for_a_non_member( repository.tag_page.assert_not_awaited() +def test_team_tag_page_is_empty_for_a_team_that_is_also_excluded( + daily_activity_client: tuple[TestClient, _FakeRepository], +) -> None: + client, repository = daily_activity_client + response: Final = client.get( + "/team/daily/activity/aggregated/tags", + params={**_entity_params("team_ids", "team-a"), "exclude_team_ids": "team-a", "offset": 0, "limit": 5}, + ) + + assert response.status_code == 200, response.text + assert response.json() == {"tags": [], "total_tags": 0, "offset": 0, "limit": 5} + repository.tag_page.assert_not_awaited() + + def test_team_aggregated_ignores_group_by_and_reads_the_team_table( daily_activity_client: tuple[TestClient, _FakeRepository], ) -> None: