From 7389344b6d0520601bfbb0fab92dc683e65100a1 Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 14:48:30 +0200 Subject: [PATCH 1/2] fix: defer terminal manager idle check until event loop is running TerminalManager was trying to create an asyncio task at module import time, but no event loop exists yet during import. This caused RuntimeError on startup. Changes: - _start_idle_check() now checks if event loop is running before creating task - If no loop exists, silently skips (will be started lazily) - Added lazy start call in get_or_create_session() when websocket connects --- apps/api/src/services/terminal_manager.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/api/src/services/terminal_manager.py b/apps/api/src/services/terminal_manager.py index 70010dc..c48c341 100644 --- a/apps/api/src/services/terminal_manager.py +++ b/apps/api/src/services/terminal_manager.py @@ -23,8 +23,14 @@ class TerminalManager: def _start_idle_check(self) -> None: """Start the idle timeout background task.""" - if self._idle_check_task is None or self._idle_check_task.done(): - self._idle_check_task = asyncio.create_task(self._idle_check_loop()) + if self._idle_check_task is not None and not self._idle_check_task.done(): + return + try: + loop = asyncio.get_running_loop() + self._idle_check_task = loop.create_task(self._idle_check_loop()) + except RuntimeError: + # No event loop running yet, will be started lazily + pass async def _idle_check_loop(self) -> None: """Periodically check for idle sessions and clean them up.""" @@ -54,6 +60,9 @@ class TerminalManager: container_id: str, ) -> TerminalSession: """Get existing session or create a new one.""" + # Ensure idle check is running (lazy start) + self._start_idle_check() + instance_id_str = str(instance_id) # Check for existing session From 5577e19782d4b0684c456b6bca0292cf856695ba Mon Sep 17 00:00:00 2001 From: Fusion Date: Sun, 24 May 2026 14:57:03 +0200 Subject: [PATCH 2/2] fix: set explicit container dimensions before xterm init - Measure parent dimensions and set them on container before term.open() - Ensures FitAddon gets correct dimensions on initialization - Prevents 1-row/1-col calculation that breaks scrolling and sizing --- apps/web/src/components/terminal.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/terminal.tsx b/apps/web/src/components/terminal.tsx index e49955d..308d02a 100644 --- a/apps/web/src/components/terminal.tsx +++ b/apps/web/src/components/terminal.tsx @@ -197,7 +197,15 @@ export const TerminalComponent: React.FC = ({ term.loadAddon(fitAddon); term.loadAddon(new WebLinksAddon()); - term.open(terminalRef.current); + // Ensure container has explicit dimensions before xterm initializes + const container = terminalRef.current; + const parentRect = container.parentElement?.getBoundingClientRect(); + if (parentRect) { + container.style.width = `${parentRect.width}px`; + container.style.height = `${parentRect.height}px`; + } + + term.open(container); // Connect WebSocket const ws = connectWebSocket();