Skip to content

Conversation

@omgate234
Copy link
Contributor

@omgate234 omgate234 commented Sep 23, 2025

Current Scenario

  1. The chat socket connection is created between server and client at the moment of initial connection.
  2. Server keeps sending updates via the fixed connection via the respective connection's request.sid

Problem
If the user reloads the page, leaves the page or checks the same session on a different device they will not be able to see live updates

Proposed Solution

  • The user joins a room with the session_id we preserve in our sessions table
  • The message are directed to a specific room which corresponds to the session_id
  • Since the user will join the same room, even in multiple reconnections, the live updates are still available

Summary by CodeRabbit

  • New Features

    • Session-scoped real-time chat: clients join a session room and messages/updates are delivered only to that room.
    • Explicit "join session" support with confirmation; improved connect/disconnect handling.
    • New public method to update session state and reflect changes in real time.
  • Refactor

    • Emissions now broadcast to session rooms with clearer logging.
    • Errors include session context for easier troubleshooting.
    • Message history persistence ensured after publish to keep conversations in sync.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 23, 2025

Walkthrough

Session-scoped Socket.IO rooms added and used for broadcasting; ChatNamespace gains connect/join/disconnect handlers and requires/joins a session_id for chat events. Message emitters now attempt real-time emission before persisting messages. Session exposes a new public update(**kwargs) -> bool to persist session attributes.

Changes

Cohort / File(s) Summary
Session emissions & API
backend/director/core/session.py
InputMessage and OutputMessage publishing now emit to room=self.session_id in namespace "/chat" with try/except logging; persistence via db.add_or_update_msg_to_conv(**self.model_dump()) is ensured after emission attempts. New public method Session.update(self, **kwargs) -> bool added to persist session attributes via db.update_session.
Socket.IO namespace room handling
backend/director/entrypoint/api/socket_io.py
ChatNamespace adds on_connect, on_join_session, on_disconnect; on_chat now requires session_id, calls join_room(session_id) and emits an error if missing, then invokes existing chat handling. Imports from flask_socketio extended to include join_room and emit.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant NS as ChatNamespace (Socket.IO)
  participant H as ChatHandler
  participant S as Session
  participant IO as Socket.IO Server
  participant DB as DB

  Note over C,NS: Client sends chat with session_id
  C->>NS: on_chat({ session_id, text, ... })
  NS->>IO: join_room(session_id)
  NS->>H: handle(message)
  H->>S: publish/update(message)
  S->>IO: emit to room=session_id (namespace "/chat")
  S->>DB: add_or_update_msg_to_conv(...)
  IO-->>C: room-scoped message/event delivered

  alt Missing session_id
    NS-->>C: emit error "session_id required"
  end
Loading
sequenceDiagram
  participant C as Client
  participant NS as ChatNamespace
  participant IO as Socket.IO Server

  C->>NS: on_join_session({ session_id })
  NS->>IO: join_room(session_id)
  NS-->>C: emit joined confirmation (room=session_id)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

I hop between the rooms tonight,
I call "join_room" beneath soft light,
I toss a byte, it finds its nest,
DB burrows hold each guest.
Carrots, logs, and events align—🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Feat/session reconnection" accurately and concisely reflects the primary change — moving socket updates to session/room-based handling so users can reconnect and continue receiving live updates; it is specific and directly related to the PR objectives. The phrasing is short, clear, and avoids unnecessary detail or noise.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/director/core/session.py (1)

403-411: emit_event should target the session room to align with session-scoped delivery

Currently emits to the namespace only; clients on reconnection may miss events. Pass room=self.session_id.

Apply this diff:

-    def emit_event(self, event: BaseEvent, namespace="/chat"):
+    def emit_event(self, event: BaseEvent, namespace="/chat"):
         """Emits a structured WebSocket event to notify all clients about updates."""
 
         event_payload = event.model_dump()
 
         try:
-            emit("event", event_payload, namespace=namespace)
-        except Exception:
-            pass
+            emit("event", event_payload, room=self.session_id, namespace=namespace)
+        except RuntimeError as e:
+            logger.warning(
+                "Error emitting event to session %s: %s",
+                self.session_id,
+                e,
+                exc_info=True,
+            )
🧹 Nitpick comments (2)
backend/director/entrypoint/api/socket_io.py (2)

11-14: Replace prints with app.logger and remove extraneous f prefix (F541)

Use Flask’s logger for consistency and to satisfy Ruff.

Apply this diff:

-    def on_connect(self):
-        """Handle client connection"""
-        print(f"Client connected to chat namespace")
+    def on_connect(self):
+        """Handle client connection"""
+        app.logger.info("Client connected to chat namespace")

38-40: Replace prints with app.logger and remove extraneous f prefix (F541)

Use Flask logger.

Apply this diff:

-    def on_disconnect(self):
-        """Handle client disconnection"""
-        print(f"Client disconnected from chat namespace")
+    def on_disconnect(self):
+        """Handle client disconnection"""
+        app.logger.info("Client disconnected from chat namespace")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e323157 and 6685f1d.

📒 Files selected for processing (2)
  • backend/director/core/session.py (2 hunks)
  • backend/director/entrypoint/api/socket_io.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
backend/director/entrypoint/api/socket_io.py (2)
backend/director/db/__init__.py (1)
  • load_db (12-19)
backend/director/handler.py (2)
  • ChatHandler (43-124)
  • chat (100-124)
backend/director/core/session.py (2)
backend/director/db/sqlite/db.py (1)
  • add_or_update_msg_to_conv (101-152)
backend/director/db/base.py (1)
  • add_or_update_msg_to_conv (25-27)
🪛 Ruff (0.13.1)
backend/director/entrypoint/api/socket_io.py

13-13: f-string without any placeholders

Remove extraneous f prefix

(F541)


40-40: f-string without any placeholders

Remove extraneous f prefix

(F541)

backend/director/core/session.py

231-231: Do not catch blind exception: Exception

(BLE001)


232-232: Use explicit conversion flag

Replace with conversion flag

(RUF010)


244-244: Do not catch blind exception: Exception

(BLE001)


245-245: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (4)
backend/director/core/session.py (3)

222-226: Docstring update reads well

Status updates now clearly mention session-room broadcast. No issues.


235-237: Docstring OK; DB-after-emit sequencing is acceptable

Sequence keeps persistence even if an emit fails. Fine as-is.


399-402: Confirm return type contract for update()

Method is annotated to return bool. Ensure db.update_session(...) indeed returns bool; otherwise adjust the annotation/docstring.

backend/director/entrypoint/api/socket_io.py (1)

4-4: Import changes look good

Using Namespace, join_room, emit is appropriate for room-scoped messaging.

Comment on lines +228 to 233
"""Push real-time update to session room."""
try:
self._publish()
except Exception as e:
print(f"Error in emitting message: {str(e)}")
print(f"Error in emitting update to session {self.session_id}: {str(e)}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Don’t swallow exceptions; replace print with proper logging and explicit conversion flag

Use logger.exception (or at least logger.warning with exc_info). Also prefer {e!s} over str(e) to satisfy RUF010. Avoid broad Exception if possible.

Apply this diff:

-    def push_update(self):
-        """Push real-time update to session room."""
-        try:
-            self._publish()
-        except Exception as e:
-            print(f"Error in emitting update to session {self.session_id}: {str(e)}")
+    def push_update(self):
+        """Push real-time update to session room."""
+        try:
+            self._publish()
+        except RuntimeError as e:
+            logger.warning("Emit failed outside request context for session %s: %s", self.session_id, e, exc_info=True)

Add the following once at the top of the file to support logging (outside the changed hunk):

import logging
logger = logging.getLogger(__name__)
🧰 Tools
🪛 Ruff (0.13.1)

231-231: Do not catch blind exception: Exception

(BLE001)


232-232: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In backend/director/core/session.py around lines 228 to 233, the except block
currently prints errors and swallows exceptions; add a module-level logger by
importing logging and creating logger = logging.getLogger(__name__) at the top
of the file, then replace the print with a logger.exception call (or
logger.warning(..., exc_info=True)) and format the message using {e!s} instead
of str(e) to satisfy RUF010; also avoid catching blanket Exception if you
can—catch the specific exceptions that _publish may raise (or re-raise after
logging) so errors aren’t silently swallowed.

Comment on lines +240 to +247
emit("chat", self.model_dump(),
room=self.session_id,
namespace="/chat")
print(f"Emitted message to session room: {self.session_id}")
except Exception as e:
print(f"Error in emitting message: {str(e)}")
self.db.add_or_update_msg_to_conv(**self.model_dump())
print(f"Error emitting to session {self.session_id}: {str(e)}")

self.db.add_or_update_msg_to_conv(**self.model_dump())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Room-scoped emit is correct; switch prints to logging and avoid blind except

Good: emit(..., room=self.session_id, namespace="/chat"). Replace print with logger and avoid catching broad Exception. Also use explicit f-string conversion flags per RUF010.

Apply this diff:

-        try:
-            emit("chat", self.model_dump(), 
-                 room=self.session_id, 
-                 namespace="/chat")
-            print(f"Emitted message to session room: {self.session_id}")
-        except Exception as e:
-            print(f"Error emitting to session {self.session_id}: {str(e)}")
+        try:
+            emit(
+                "chat",
+                self.model_dump(),
+                room=self.session_id,
+                namespace="/chat",
+            )
+            logger.info("Emitted message to session room: %s", self.session_id)
+        except RuntimeError as e:
+            logger.warning(
+                "Error emitting to session %s: %s",
+                self.session_id,
+                e,
+                exc_info=True,
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
emit("chat", self.model_dump(),
room=self.session_id,
namespace="/chat")
print(f"Emitted message to session room: {self.session_id}")
except Exception as e:
print(f"Error in emitting message: {str(e)}")
self.db.add_or_update_msg_to_conv(**self.model_dump())
print(f"Error emitting to session {self.session_id}: {str(e)}")
self.db.add_or_update_msg_to_conv(**self.model_dump())
try:
emit(
"chat",
self.model_dump(),
room=self.session_id,
namespace="/chat",
)
logger.info("Emitted message to session room: %s", self.session_id)
except RuntimeError as e:
logger.warning(
"Error emitting to session %s: %s",
self.session_id,
e,
exc_info=True,
)
self.db.add_or_update_msg_to_conv(**self.model_dump())
🧰 Tools
🪛 Ruff (0.13.1)

244-244: Do not catch blind exception: Exception

(BLE001)


245-245: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In backend/director/core/session.py around lines 240-247, replace the two
print() calls with the module logger (e.g., logger.info or logger.debug for the
success message, and logger.error or logger.exception for failures), and stop
using a blind "except Exception"; instead catch the specific
socketio/emit-related exceptions you expect (or at minimum re-raise after
logging). When logging the exception include the exception object with an
explicit conversion flag (e.g., {e!r}) or use logger.exception(...,
exc_info=True) to record the traceback, and ensure
db.add_or_update_msg_to_conv(**self.model_dump()) still runs in the appropriate
place (either after successful emit or in a finally if it must always run).

Comment on lines +16 to +24
"""Handle chat messages and auto-join session room"""
session_id = message.get('session_id')
if not session_id:
emit('error', {'message': 'session_id is required'})
return

join_room(session_id)
print(f"Client joined session room {session_id}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

🧩 Analysis chain

Auto-join is correct; prefer logging over print and consider event name for errors

  • Good: requiring session_id and joining the room.
  • Swap print for app.logger.
  • Verify that emitting 'error' as an event name matches client handlers and isn’t conflicting with any reserved handling; consider a namespaced event like 'chat_error' if not already standardized.

Apply this diff for logging:

-        if not session_id:
-            emit('error', {'message': 'session_id is required'})
+        if not session_id:
+            emit('error', {'message': 'session_id is required'})
             return
             
         join_room(session_id)
-        print(f"Client joined session room {session_id}")
+        app.logger.info("Client joined session room %s", session_id)

Also confirm client expectations:


🌐 Web query:

Is 'error' a reserved or special event name in Socket.IO client handlers that could interfere with custom error messaging? Should we prefer a custom event name like 'chat_error'?

💡 Result:

Short answer: Yes — "error" is a built‑in Socket.IO event (fired on connection/transport errors) and is treated as a special/reserved event name, so using it for your own application-level error messages can conflict or be confusing. Use a custom name such as "chat_error" (or any app‑specific name) for application error payloads. [1][2][3]

Sources:

  • Official Socket.IO docs — client/server event list (shows "error" as an event). [1][2]
  • Community guidance (StackOverflow) recommending app‑specific event names instead of reusing "error". [3]

Replace print with logger and stop emitting reserved 'error' event — use an app-specific event (e.g. 'chat_error')

  • In backend/director/entrypoint/api/socket_io.py replace print(...) with app.logger.info("Client joined session room %s", session_id).
  • Do not emit('error', ...); "error" is a built-in Socket.IO event. Emit a custom event (e.g. emit('chat_error', {'message': 'session_id is required'})) and update client handlers to listen for that name.
  • Verify no other server/client code expects application-level messages on the generic "error" event and update those callers/handlers accordingly.
🤖 Prompt for AI Agents
In backend/director/entrypoint/api/socket_io.py around lines 16 to 24, replace
the print statement with app.logger.info("Client joined session room %s",
session_id) and stop emitting the Socket.IO reserved "error" event; instead emit
a custom application event such as emit('chat_error', {'message': 'session_id is
required'}) when session_id is missing. Update any client/server handlers that
expect application-level errors on "error" to listen for the new event name, and
scan the codebase to ensure no other components rely on application messages
using the generic "error" event before deploying.

Comment on lines +30 to +37
def on_join_session(self, data):
"""Explicitly join a session room (for reconnection)"""
session_id = data.get('session_id')
if session_id:
join_room(session_id)
emit('session_joined', {'session_id': session_id})
print(f"Client explicitly joined session room {session_id}")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick

Add else-branch for missing session_id and replace print with logging

If session_id is missing, emit an error back to the client. Use app.logger instead of print.

Apply this diff:

     def on_join_session(self, data):
         """Explicitly join a session room (for reconnection)"""
         session_id = data.get('session_id')
-        if session_id:
-            join_room(session_id)
-            emit('session_joined', {'session_id': session_id})
-            print(f"Client explicitly joined session room {session_id}")
+        if session_id:
+            join_room(session_id)
+            emit('session_joined', {'session_id': session_id})
+            app.logger.info("Client explicitly joined session room %s", session_id)
+        else:
+            emit('error', {'message': 'session_id is required to join a session'})
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def on_join_session(self, data):
"""Explicitly join a session room (for reconnection)"""
session_id = data.get('session_id')
if session_id:
join_room(session_id)
emit('session_joined', {'session_id': session_id})
print(f"Client explicitly joined session room {session_id}")
def on_join_session(self, data):
"""Explicitly join a session room (for reconnection)"""
session_id = data.get('session_id')
if session_id:
join_room(session_id)
emit('session_joined', {'session_id': session_id})
app.logger.info("Client explicitly joined session room %s", session_id)
else:
emit('error', {'message': 'session_id is required to join a session'})
🤖 Prompt for AI Agents
In backend/director/entrypoint/api/socket_io.py around lines 30 to 37, the
on_join_session handler lacks an else-branch to handle a missing session_id and
uses print for logging; modify the function to emit an error event back to the
client (e.g., emit('error', {'message': 'session_id required'})) when session_id
is falsy and replace the print statement with app.logger (use app.logger.info or
app.logger.error as appropriate) to log the join action and error; ensure you
import or reference the Flask app context correctly if needed so app.logger is
available.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
backend/director/core/session.py (1)

211-219: Replace prints and blind except with logging; decide on emit vs. persist ordering

  • Avoid print and broad Exception; use module logger and catch RuntimeError from Flask‑SocketIO when outside request context. Also fixes RUF010.
  • Input vs. Output ordering is inconsistent: InputMessage persists before emitting; OutputMessage emits before persisting. Pick one policy (persist‑then‑emit for durability, or emit‑then‑persist for lower latency) and apply consistently.

Apply within this hunk:

-        self.db.add_or_update_msg_to_conv(**self.model_dump(exclude={"db"}))
-        try:
-            emit("chat", self.model_dump(exclude={"db"}), 
-                 room=self.session_id, 
-                 namespace="/chat")
-            print(f"Broadcasted input message to session room: {self.session_id}")
-        except Exception as e:
-            print(f"Error broadcasting input message to session {self.session_id}: {str(e)}")
+        self.db.add_or_update_msg_to_conv(**self.model_dump(exclude={"db"}))
+        try:
+            emit(
+                "chat",
+                self.model_dump(exclude={"db"}),
+                room=self.session_id,
+                namespace="/chat",
+            )
+            logger.info("Broadcasted input message to session room: %s", self.session_id)
+        except RuntimeError as e:
+            logger.warning(
+                "Error broadcasting input message to session %s: %s",
+                self.session_id,
+                e,
+                exc_info=True,
+            )

Add once at the top of the file:

import logging
logger = logging.getLogger(__name__)

Would you like me to align both Input and Output flows to the same ordering? If yes, which policy do you prefer (persist‑then‑emit vs emit‑then‑persist)?

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6685f1d and 798b828.

📒 Files selected for processing (1)
  • backend/director/core/session.py (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/director/core/session.py (2)
backend/director/db/sqlite/db.py (1)
  • add_or_update_msg_to_conv (101-152)
backend/director/db/base.py (1)
  • add_or_update_msg_to_conv (25-27)
🪛 Ruff (0.13.1)
backend/director/core/session.py

218-218: Do not catch blind exception: Exception

(BLE001)


219-219: Use explicit conversion flag

Replace with conversion flag

(RUF010)


238-238: Do not catch blind exception: Exception

(BLE001)


239-239: Use explicit conversion flag

Replace with conversion flag

(RUF010)


251-251: Do not catch blind exception: Exception

(BLE001)


252-252: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🔇 Additional comments (5)
backend/director/core/session.py (5)

229-233: LGTM on the clarification

Docstring accurately reflects the broadcast behavior of update_status.


234-240: Don’t swallow exceptions; replace print with logger and narrow the except

Use logger and catch RuntimeError; keep traceback.

     def push_update(self):
-        """Push real-time update to session room."""
-        try:
-            self._publish()
-        except Exception as e:
-            print(f"Error in emitting update to session {self.session_id}: {str(e)}")
+        """Push real-time update to session room."""
+        try:
+            self._publish()
+        except RuntimeError as e:
+            logger.warning(
+                "Emit failed outside request context for session %s: %s",
+                self.session_id,
+                e,
+                exc_info=True,
+            )

241-244: LGTM on publish docstring

Matches the method’s behavior.


245-254: Use logger instead of prints; avoid blind except; ensure DB persist in finally

  • Replace prints with logger and catch RuntimeError to satisfy BLE001/RUF010.
  • Wrap DB persistence in finally to guarantee execution even if emit raises.
-    def _publish(self):
-        try:
-            emit("chat", self.model_dump(), 
-                 room=self.session_id, 
-                 namespace="/chat")
-            print(f"Emitted message to session room: {self.session_id}")
-        except Exception as e:
-            print(f"Error emitting to session {self.session_id}: {str(e)}")
-
-        self.db.add_or_update_msg_to_conv(**self.model_dump())
+    def _publish(self):
+        payload = self.model_dump()
+        try:
+            emit(
+                "chat",
+                payload,
+                room=self.session_id,
+                namespace="/chat",
+            )
+            logger.info("Emitted message to session room: %s", self.session_id)
+        except RuntimeError as e:
+            logger.warning(
+                "Error emitting to session %s: %s",
+                self.session_id,
+                e,
+                exc_info=True,
+            )
+        finally:
+            try:
+                self.db.add_or_update_msg_to_conv(**payload)
+            except Exception:
+                logger.exception(
+                    "Failed to persist message %s for session %s",
+                    self.msg_id,
+                    self.session_id,
+                )

Note: keep the logger import and instance at module scope as suggested earlier.


406-409: Confirm BaseDB declares update_session or narrow the db type

backend/director/core/session.py (lines 406–409) calls self.db.update_session — if BaseDB doesn't declare update_session this will raise AttributeError at runtime and breaks static typing.

  • Either add update_session(...) to the BaseDB ABC/Protocol with the correct signature; or
  • Narrow the type of self.db (or the field/parameter) to a concrete class or Protocol that exposes update_session.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant