From 0cb2eefd29c526bdbcdea258541532d2f87712eb Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 20:47:14 +0200 Subject: [PATCH] fix: send SIGWINCH to docker exec process for container terminal resize Instead of sending stty commands through the user's terminal session (which causes 'inappropriate ioctl' errors), send SIGWINCH signal to the docker exec process on the host. Docker exec should forward this to the container process, causing the shell to re-read its terminal size. This avoids: - Visible stty commands in the terminal - ioctl errors from stty - Interference with user's shell session --- apps/api/src/services/terminal_session.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apps/api/src/services/terminal_session.py b/apps/api/src/services/terminal_session.py index 034b86f..0557a8b 100644 --- a/apps/api/src/services/terminal_session.py +++ b/apps/api/src/services/terminal_session.py @@ -5,6 +5,7 @@ import logging import os import pty import select +import signal import struct import fcntl import time @@ -153,14 +154,18 @@ class TerminalSession: 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}") + # so host PTY resize doesn't propagate to the container shell. + # Send SIGWINCH to the docker exec process on the host. + # Docker exec forwards signals to the container process, which should + # cause the container's shell to re-read its terminal size. + if self.process and self.process.pid: + try: + os.kill(self.process.pid, signal.SIGWINCH) + logger.debug(f"Sent SIGWINCH to docker exec process {self.process.pid} for session {self.session_id}") + except ProcessLookupError: + logger.warning(f"docker exec process {self.process.pid} not found for session {self.session_id}") + except Exception as e: + logger.warning(f"Failed to send SIGWINCH: {e}") async def reset(self) -> None: """Reset the session by killing the process and clearing state."""