fix: send SIGWINCH to docker exec after PTY resize

When resizing the PTY, docker exec needs to be notified so it can
re-read the terminal size and propagate it to the container's PTY.
Without this, the container shell stays at 80x24 regardless of what
the frontend sends.
This commit is contained in:
Fusion
2026-05-24 15:57:29 +02:00
parent 5500552993
commit 0dba13a354
2 changed files with 25 additions and 7 deletions
+4 -4
View File
@@ -148,10 +148,10 @@ async def _write_loop(session, websocket) -> None:
msg_type = ctrl.get("type")
if msg_type == "resize":
await session.resize(
ctrl.get("cols", 80),
ctrl.get("rows", 24),
)
cols = ctrl.get("cols", 80)
rows = ctrl.get("rows", 24)
logger.info(f"Received resize message for instance {instance_id}: {cols}x{rows}")
await session.resize(cols, rows)
elif msg_type == "reset":
# Reset terminal session
logger.info("Resetting terminal session for instance %s", session.instance_id)
+21 -3
View File
@@ -1,9 +1,11 @@
"""Terminal session management for tool instances."""
import asyncio
import logging
import os
import pty
import select
import signal
import struct
import fcntl
import time
@@ -11,6 +13,8 @@ import uuid
from collections import deque
from typing import Any
logger = logging.getLogger(__name__)
class TerminalSession:
"""Manages a single terminal session connected to a docker container.
@@ -55,6 +59,7 @@ class TerminalSession:
# Set the terminal size initially
self._set_terminal_size(self._cols, self._rows)
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
@@ -79,16 +84,27 @@ class TerminalSession:
self.last_activity = time.time()
def _set_terminal_size(self, cols: int, rows: int) -> None:
"""Set the terminal size using TIOCSWINSZ."""
"""Set the terminal size using TIOCSWINSZ and signal docker exec."""
if self._master_fd is None:
logger.warning("Cannot resize: master_fd is None (session not started)")
return
# TIOCSWINSZ = 0x5414 on Linux
TIOCSWINSZ = 0x5414
size = struct.pack('HHHH', rows, cols, 0, 0)
try:
fcntl.ioctl(self._master_fd, TIOCSWINSZ, size)
except (OSError, IOError):
pass
logger.info(f"Resized PTY to {cols}x{rows} (fd={self._master_fd})")
# Send SIGWINCH to docker exec process so it re-reads terminal
# size and propagates it to the container's PTY
if self.process and self.process.pid:
try:
os.kill(self.process.pid, signal.SIGWINCH)
logger.info(f"Sent SIGWINCH to docker exec pid={self.process.pid}")
except (OSError, ProcessLookupError) as e:
logger.warning(f"Failed to send SIGWINCH: {e}")
except (OSError, IOError) as e:
logger.error(f"Failed to resize PTY: {e}")
async def read_output(self) -> bytes:
"""Read output from the PTY master and store in buffer."""
@@ -134,9 +150,11 @@ class TerminalSession:
async def resize(self, cols: int, rows: int) -> None:
"""Resize the terminal."""
if self._closed:
logger.warning("Cannot resize: session is closed")
return
self._cols = cols
self._rows = rows
logger.info(f"resize() called for session {self.session_id}: {cols}x{rows}")
self._set_terminal_size(cols, rows)
async def reset(self) -> None: