Description of the Bug
In backend/app/routes/chat.py, _save_message() falls back to an unordered .first() query when session_id is not supplied:
if not session_id:
session = db.query(ChatSession).filter(
ChatSession.user_id == user_id
).first() # no order_by
Once any session exists, .first() deterministically keeps returning the OLDEST session (not the most recent, not one scoped to the current document), so every fallback-triggered message keeps piling into that same original session going forward.
This fallback is reachable in real, non-contrived flows:
- A brand-new user's very first message. ChatRequest.session_id is Optional (schemas.py), and frontend/src/store/chat-store.ts only auto-selects an existing session in fetchSessions() - there's nothing to select before any session exists, so the first question a new user asks (a completely normal flow - open a doc, start typing) is sent with session_id: null.
- Any transient failure of GET /chat/sessions. The catch block in fetchSessions() unconditionally resets activeSessionId to null:
} catch (err) {
set({ sessions: [], activeSessionId: null });
}
This fires on any network blip or backend hiccup while fetching sessions - even for a returning user with an established, valid active session - and silently reroutes their next message away from their real conversation.
The dedicated "New Chat" flow (ChatSessionSidebar.tsx -> createSession()) is unaffected, since it always creates a real session via POST /chat/sessions before use.
Steps to Reproduce
- Register a new account (zero existing chat sessions).
- Upload a document and immediately ask a question without pressing "New Chat" first.
- Ask a question about a second, unrelated document the same way.
- Call GET /chat/sessions - both conversations are merged into a single "Default Chat" session instead of being scoped separately.
Alternative repro for trigger path 2:
- As an existing user with an active session, block/fail the GET /api/v1/chat/sessions request (devtools network throttling or temporarily stopping the backend during that call).
- Ask a question.
- Observe it lands in the oldest session, not the one you were just in.
Expected Behavior
When no session_id is supplied, the backend should create a fresh session scoped to the current context (or the frontend should create one via
POST /chat/sessions before sending the first message), rather than silently reusing an unrelated pre-existing session via an unordered .first() query.
Screenshots / Logs
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG> Get-ChildItem backend/app -Recurse -File | Select-String -Pattern "_save_message" | Format-Table Path, LineNumber, Line
Path LineNumber Line
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 181 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 672 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 673 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 802 _save_me...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 871 ...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 1086 def _save_me...
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG> Get-ChildItem frontend/src -Recurse -File | Select-String -Pattern "activeSessionId"
frontend\src\components\chat\ChatPanel.tsx:86: const activeSessionId = useChatStore((state) =>
state.activeSessionId);
frontend\src\components\chat\ChatPanel.tsx:163: // Load history on activeSessionId or fallback to activeDoc change
frontend\src\components\chat\ChatPanel.tsx:165: if (activeSessionId) {
frontend\src\components\chat\ChatPanel.tsx:166: fetchSessionHistory(activeSessionId);
frontend\src\components\chat\ChatPanel.tsx:212: }, [activeSessionId, activeDoc, fetchSessionHistory, setMessages]);
frontend\src\components\chat\ChatPanel.tsx:277: session_id: activeSessionId,
frontend\src\components\chat\ChatPanel.tsx:404: session_id: activeSessionId,
frontend\src\components\chat\ChatSessionSidebar.tsx:12: const activeSessionId = useChatStore((state) =>
state.activeSessionId);
frontend\src\components\chat\ChatSessionSidebar.tsx:17: const setActiveSessionId = useChatStore((state) =>
state.setActiveSessionId);
frontend\src\components\chat\ChatSessionSidebar.tsx:79: setActiveSessionId(id);
frontend\src\components\chat\ChatSessionSidebar.tsx:130: const isActive = session.id === activeSessionId;
frontend\src\store\chat-store.ts:50: activeSessionId: string | null;
frontend\src\store\chat-store.ts:61: setActiveSessionId: (value: Setter<string | null>) => void;
frontend\src\store\chat-store.ts:80: activeSessionId: null,
frontend\src\store\chat-store.ts:109: setActiveSessionId(value) {
frontend\src\store\chat-store.ts:111: activeSessionId: resolveValue(value, state.activeSessionId),
frontend\src\store\chat-store.ts:125: if (data.length > 0 && !get().activeSessionId) {
frontend\src\store\chat-store.ts:126: set({ activeSessionId: data[0].id });
frontend\src\store\chat-store.ts:135: activeSessionId: null,
frontend\src\store\chat-store.ts:147: activeSessionId: session.id,
frontend\src\store\chat-store.ts:177: let nextActiveId = state.activeSessionId;
frontend\src\store\chat-store.ts:178: if (state.activeSessionId === id) {
frontend\src\store\chat-store.ts:183: activeSessionId: nextActiveId,
frontend\src\store\chat-store.ts:186: const activeId = get().activeSessionId;
frontend\src\store\chat-store.ts:220: activeSessionId: null,
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG>
Environment
Windows 11
GSSoC '26
Description of the Bug
In backend/app/routes/chat.py, _save_message() falls back to an unordered
.first()query when session_id is not supplied:Once any session exists,
.first()deterministically keeps returning the OLDEST session (not the most recent, not one scoped to the current document), so every fallback-triggered message keeps piling into that same original session going forward.This fallback is reachable in real, non-contrived flows:
} catch (err) {
set({ sessions: [], activeSessionId: null });
}
This fires on any network blip or backend hiccup while fetching sessions - even for a returning user with an established, valid active session - and silently reroutes their next message away from their real conversation.
The dedicated "New Chat" flow (ChatSessionSidebar.tsx -> createSession()) is unaffected, since it always creates a real session via POST /chat/sessions before use.
Steps to Reproduce
Alternative repro for trigger path 2:
Expected Behavior
When no session_id is supplied, the backend should create a fresh session scoped to the current context (or the frontend should create one via
POST /chat/sessions before sending the first message), rather than silently reusing an unrelated pre-existing session via an unordered
.first()query.Screenshots / Logs
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG> Get-ChildItem backend/app -Recurse -File | Select-String -Pattern "_save_message" | Format-Table Path, LineNumber, Line
Path LineNumber Line
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 181 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 672 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 673 _sav...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 802 _save_me...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 871 ...
C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG\backend\app\routes\chat.py 1086 def _save_me...
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG> Get-ChildItem frontend/src -Recurse -File | Select-String -Pattern "activeSessionId"
frontend\src\components\chat\ChatPanel.tsx:86: const activeSessionId = useChatStore((state) =>
state.activeSessionId);
frontend\src\components\chat\ChatPanel.tsx:163: // Load history on activeSessionId or fallback to activeDoc change
frontend\src\components\chat\ChatPanel.tsx:165: if (activeSessionId) {
frontend\src\components\chat\ChatPanel.tsx:166: fetchSessionHistory(activeSessionId);
frontend\src\components\chat\ChatPanel.tsx:212: }, [activeSessionId, activeDoc, fetchSessionHistory, setMessages]);
frontend\src\components\chat\ChatPanel.tsx:277: session_id: activeSessionId,
frontend\src\components\chat\ChatPanel.tsx:404: session_id: activeSessionId,
frontend\src\components\chat\ChatSessionSidebar.tsx:12: const activeSessionId = useChatStore((state) =>
state.activeSessionId);
frontend\src\components\chat\ChatSessionSidebar.tsx:17: const setActiveSessionId = useChatStore((state) =>
state.setActiveSessionId);
frontend\src\components\chat\ChatSessionSidebar.tsx:79: setActiveSessionId(id);
frontend\src\components\chat\ChatSessionSidebar.tsx:130: const isActive = session.id === activeSessionId;
frontend\src\store\chat-store.ts:50: activeSessionId: string | null;
frontend\src\store\chat-store.ts:61: setActiveSessionId: (value: Setter<string | null>) => void;
frontend\src\store\chat-store.ts:80: activeSessionId: null,
frontend\src\store\chat-store.ts:109: setActiveSessionId(value) {
frontend\src\store\chat-store.ts:111: activeSessionId: resolveValue(value, state.activeSessionId),
frontend\src\store\chat-store.ts:125: if (data.length > 0 && !get().activeSessionId) {
frontend\src\store\chat-store.ts:126: set({ activeSessionId: data[0].id });
frontend\src\store\chat-store.ts:135: activeSessionId: null,
frontend\src\store\chat-store.ts:147: activeSessionId: session.id,
frontend\src\store\chat-store.ts:177: let nextActiveId = state.activeSessionId;
frontend\src\store\chat-store.ts:178: if (state.activeSessionId === id) {
frontend\src\store\chat-store.ts:183: activeSessionId: nextActiveId,
frontend\src\store\chat-store.ts:186: const activeId = get().activeSessionId;
frontend\src\store\chat-store.ts:220: activeSessionId: null,
PS C:\Users\ankit\OneDrive\Desktop\work\Projects\PDF-Assistant-RAG>
Environment
Windows 11
GSSoC '26