fix(terminal): prevent double session creation, restore sessions on reload

Three fixes for multi-session terminal bugs:

1. Race-condition double creation: The auto-create effect fired twice because
   loadSessions returned 0 while an earlier createSession was still in flight.
   Added hasAutoCreated guard ref to ensure only one auto-create happens.

2. Page reload spawns new sessions: After server restart, list_terminal_sessions
   filtered out DB-only sessions (no in-memory counterpart), so the frontend
   thought no sessions existed and auto-created new ones. Reverted the filter
   so DB rows are always returned. The WebSocket handler now restores the
   in-memory session from the DB row on demand when connecting.

3. No input after connection: The backend _read_loop would break on any send
   error, causing asyncio.wait to cancel the _write_loop. Made _read_loop
   retry up to 3 times before giving up, preventing transient send errors
   from killing input handling.

Quality gates: pytest (15/15 passed), tsc clean
This commit is contained in:
2026-05-28 19:45:59 +02:00
parent 9ccaae04db
commit b6e71e32f5
3 changed files with 64 additions and 29 deletions
+4 -2
View File
@@ -131,6 +131,7 @@ class TerminalManager:
container_id: str,
startup_command: str | None = None,
name: str | None = None,
session_id: str | None = None,
) -> TerminalSession:
"""Create a new terminal session for an instance.
@@ -159,7 +160,8 @@ class TerminalManager:
instance_id_str, self.MAX_SESSIONS_PER_INSTANCE
)
session_id = str(uuid.uuid4())
if session_id is None:
session_id = str(uuid.uuid4())
session = TerminalSession(
session_id=session_id,
instance_id=instance_id,
@@ -172,7 +174,7 @@ class TerminalManager:
key = (instance_id_str, session_id)
self._sessions[key] = session
# Fire-and-forget DB insert
# Fire-and-forget DB insert (skip if row already exists)
asyncio.create_task(
self._insert_db_session_row(session_id, instance_id, session.name)
)