From 5f5dc9c85116cf8788cbc1e99edcf5917e96041f Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 20:40:02 +0200 Subject: [PATCH] fix: revert docker exec -i change and use stty with line hiding Reverted docker exec back to -it (required for interactive bash). Instead, sends stty command with \r to hide it from the terminal display: - \r moves cursor to start of line (overwrites prompt) - stty command executes silently (no output on success) - \r moves cursor back to start, hiding echoed command This sends stty on EVERY resize so the container shell always matches frontend dimensions. --- apps/api/src/services/terminal_session.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/api/src/services/terminal_session.py b/apps/api/src/services/terminal_session.py index a8c1f50..034b86f 100644 --- a/apps/api/src/services/terminal_session.py +++ b/apps/api/src/services/terminal_session.py @@ -61,15 +61,11 @@ 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 -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 + # Using -it because the slave fd IS a TTY self.process = await asyncio.create_subprocess_exec( "docker", "exec", - "-i", + "-it", "-e", "TERM=xterm", self.container_id, @@ -155,6 +151,16 @@ 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 -it creates its own PTY inside the container, + # so host PTY resize doesn't propagate. We must send stty manually. + # To hide the command from the user: + # 1. \r moves cursor to start of current line (overwrites prompt) + # 2. stty command executes silently (no output on success) + # 3. \r moves cursor back to start, hiding the echoed command + stty_cmd = f"\rstty cols {cols} rows {rows}\r".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."""