fix(terminal): verify container exists before creating terminal session

The instance status may say 'running' but the actual Docker container
may have been removed (e.g. docker prune, host restart). The old code
created a terminal session which immediately died because docker exec
failed with 'No such container'.

- Add get_container_status check in WebSocket handler before session creation
- Return 4004 with clear message if container is missing
- This prevents spawning zombie terminal sessions
This commit is contained in:
2026-05-28 21:18:47 +02:00
parent a3d01dd0a5
commit c7c4cb45a7
2 changed files with 25 additions and 3 deletions
+15
View File
@@ -124,6 +124,21 @@ async def _handle_terminal_websocket(
logger.debug("Terminal auth passed for instance %s, user %s", instance_id, user_id)
# Verify the container actually exists (may have been removed/recreated)
from src.services.docker import get_container_status
container_status = get_container_status(instance.container_id)
if container_status["status"] == "not_found":
logger.error(
"Container %s for instance %s not found (may have been removed)",
instance.container_id,
instance_id,
)
await websocket.close(
code=4004, reason="Container not found — restart the tool instance"
)
return
# Fetch tool type to get startup_command
tool_type = await db_session.get(ToolType, instance.tool_type_id)
startup_command = tool_type.startup_command if tool_type else None
+10 -3
View File
@@ -140,7 +140,9 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
if (event.data instanceof Blob) {
// eslint-disable-next-line no-console
console.log(`[Terminal ${sessionId ?? "default"}] received ${(event.data as Blob).size} bytes`);
console.log(
`[Terminal ${sessionId ?? "default"}] received ${(event.data as Blob).size} bytes`,
);
event.data.arrayBuffer().then((buffer) => {
const data = new Uint8Array(buffer);
termRef.current?.write(data);
@@ -311,7 +313,9 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// Open xterm first (must happen before fit)
term.open(container);
term.focus();
console.log(`[Terminal ${sessionId ?? "default"}] xterm opened and focused`);
console.log(
`[Terminal ${sessionId ?? "default"}] xterm opened and focused`,
);
const ws = connectWebSocket();
// Initial fit after layout settles (terminal must be opened first)
@@ -337,7 +341,10 @@ export const TerminalComponent = React.forwardRef<TerminalRef, TerminalProps>(
// Handle terminal input
term.onData((data) => {
// eslint-disable-next-line no-console
console.log(`[Terminal ${sessionId ?? "default"}] sending:`, JSON.stringify(data));
console.log(
`[Terminal ${sessionId ?? "default"}] sending:`,
JSON.stringify(data),
);
const currentWs = wsRef.current;
if (currentWs?.readyState !== WebSocket.OPEN) return;