From 29e4bed9e66cb14f8346e594680641462b9e97d4 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 20:35:07 +0200 Subject: [PATCH] fix: use docker exec -i instead of -it so host PTY resize propagates to container Removes -t flag from docker exec so it uses our PTY slave directly instead of creating its own PTY inside the container. This allows TIOCSWINSZ on the host PTY master to propagate naturally to the container shell via SIGWINCH. Also removes all stty command injection logic since resize now works natively. --- apps/api/src/services/terminal_session.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) 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."""