diff --git a/apps/api/src/services/terminal_session.py b/apps/api/src/services/terminal_session.py index db26918..a8c1f50 100644 --- a/apps/api/src/services/terminal_session.py +++ b/apps/api/src/services/terminal_session.py @@ -61,11 +61,15 @@ class TerminalSession: logger.info(f"Starting terminal session {self.session_id} for container {self.container_id} with initial size {self._cols}x{self._rows}") # Start docker exec with the slave fd as stdin/stdout/stderr - # Using -it because the slave fd IS a TTY + # Using -i (interactive) but NOT -t (tty) because: + # 1. The slave fd IS a TTY + # 2. docker exec -t creates its OWN PTY inside the container + # 3. This prevents host PTY resize from propagating to the container shell + # By using only -i, docker exec uses our PTY slave directly self.process = await asyncio.create_subprocess_exec( "docker", "exec", - "-it", + "-i", "-e", "TERM=xterm", self.container_id, @@ -151,17 +155,6 @@ class TerminalSession: self._rows = rows logger.info(f"resize() called for session {self.session_id}: {cols}x{rows}") self._set_terminal_size(cols, rows) - - # Docker exec doesn't forward PTY resize to the container process, - # so we need to explicitly set the size inside the container shell. - # Send on every resize so the container shell always matches the frontend. - # Use stty -echo to prevent the command from being visible, then clear the line. - stty_cmd = ( - f"stty -echo; stty cols {cols} rows {rows}; stty echo\n" - f"\x1b[A\x1b[M" # Move up 1 line and delete it (clears the stty command) - ).encode() - await self.write_input(stty_cmd) - logger.debug(f"Sent stty resize to container for session {self.session_id}: {cols}x{rows}") async def reset(self) -> None: """Reset the session by killing the process and clearing state."""