Repository navigation
FirestoreSessionService.list_sessions(user_id="") returns every user's sessions #7662
Description
🔴 Required Information
Describe the Bug:
FirestoreSessionService.list_sessions(app_name=..., user_id="") returns the sessions of every user of the app, not only the sessions of user "".
src/google/adk/integrations/firestore/firestore_session_service.py checks if user_id: (twice in list_sessions), so an empty string falls through to the collection_group query that user_id=None uses to list all users. The other session services (InMemory, Sqlite, Database, Redis, VertexAI) treat "" as a user id and return only that user's sessions. #7171 fixed the same check in SqliteSessionService.
This matters when the user id comes from a request or auth context that can be empty: the caller gets other users' sessions back.
Steps to Reproduce:
pip install google-adk==2.11.0 google-cloud-firestore- Run the snippet below. It uses a mocked Firestore client:
collection_groupholds two other users' sessions, and the per-user collection for""is empty.
Expected Behavior:
[] collection_group used: False
Observed Behavior:
[('alice', 's-alice'), ('bob', 's-bob')] collection_group used: True
Environment Details:
- ADK Library Version: 2.11.0 (also reproduced on
mainat d324744) - Desktop OS: macOS
- Python Version: 3.12.13
Model Information:
- Are you using LiteLLM: No
- Which model is being used: N/A
🟡 Optional Information
Minimal Reproduction Code:
import asyncio
from unittest import mock
from google.adk.integrations.firestore.firestore_session_service import FirestoreSessionService
def doc(data):
snapshot = mock.MagicMock(exists=True)
snapshot.to_dict.return_value = data
return snapshot
async def main():
client = mock.MagicMock()
# Sessions of two other users, returned by the all-users collection_group query.
client.collection_group.return_value.where.return_value.get = mock.AsyncMock(
return_value=[
doc({"id": "s-alice", "appName": "app", "userId": "alice", "state": {}}),
doc({"id": "s-bob", "appName": "app", "userId": "bob", "state": {}}),
]
)
# Every per-user lookup (sessions of user "", app and user state) is empty.
any_ref = client.collection.return_value.document.return_value
any_ref.get = mock.AsyncMock(return_value=mock.MagicMock(exists=False))
user_ref = any_ref.collection.return_value.document.return_value
user_ref.get = mock.AsyncMock(return_value=mock.MagicMock(exists=False))
user_ref.collection.return_value.where.return_value.get = mock.AsyncMock(
return_value=[]
)
async def get_all(refs):
for _ in refs:
yield mock.MagicMock(exists=False)
client.get_all = get_all
service = FirestoreSessionService(client=client)
response = await service.list_sessions(app_name="app", user_id="")
print(
[(s.user_id, s.id) for s in response.sessions],
"collection_group used:",
client.collection_group.called,
)
asyncio.run(main())How often has this issue occurred?:
- Always (100%)
Proposed fix: Use if user_id is not None: in both places in list_sessions, as #7171 did for SqliteSessionService. I have a PR ready with a unit test.
Activity
surajksharma07 commentedon Oct 22, 2026
Reproduced this on 2.12.0 too @AkshayShah03, list_sessions(user_id="") still falls through to the collection_group query.
As a workaround until #7663 lands you could subclass FirestoreSessionService and return an empty ListSessionsResponse when user_id == "" before calling super().list_sessions(). The early return in af160d6 looks like the right approach.
Could you give that a try in your setup and confirm it holds up for "", None and a normal user_id? Once you’ve verified it end to end please update the PR if anything changes so the reviewers can take it forward.
AkshayShah03 commentedon Oct 22, 2026
Thanks @surajksharma07. Yes, I verified af160d6 end to end against the Firestore emulator (cloud-firestore-emulator v1.19.8) with sessions for two users:
user_id=""→[](onmainit returns both users' sessions)user_id=None→ all sessions for the app, unchangeduser_id="alice"→ only alice's session, unchangedcreate_session(user_id="")is rejected by the backend (400 ... lacks a resource id), so no session can exist for"". The PR description has the details; nothing else needs to change.