OpenEnv WebSocket Error Handling and Timeout Configuration: Complete Guide
OpenEnv uses a FastAPI WebSocket endpoint at /ws with structured error responses via WSErrorResponse, while separate timeout mechanisms control session idle duration through ConcurrencyConfig.session_timeout and tool execution limits via MCP_TOOL_CALL_TIMEOUT.
OpenEnv, an open-source environment platform from Hugging Face, runs interactive sessions over persistent WebSocket connections. Understanding how the server handles malformed messages, unexpected exceptions, and timeout scenarios is essential for building robust client applications that interact with the environment server.
How OpenEnv Handles WebSocket Errors
The WebSocket handler resides in src/openenv/core/env_server/http_server.py and manages the full lifecycle of client connections. When a client connects to the /ws endpoint, the server establishes a dedicated session and enters a message processing loop.
Connection Lifecycle and Message Dispatch
The connection flow follows three distinct phases:
- Connection setup – Upon receiving a client connection, the server calls
await websocket.accept()and initializes a dedicated environment instance through_create_session. - Message loop – The server continuously reads text frames, parses them as JSON, and dispatches commands based on the
"type"field (reset,step,state, orclose). - Session cleanup – When the client disconnects or timeouts occur, the server tears down the environment instance and releases resources.
Structured Error Responses
All errors are wrapped in the WSErrorResponse model defined in src/openenv/core/env_server/types.py to ensure clients never receive raw Python tracebacks:
class WSErrorResponse(BaseModel):
"""WebSocket response for errors."""
type: Literal["error"] = Field(default="error")
data: Dict[str, Any] = Field(
description="Error details including message and code"
)
The server handles three specific error categories:
- Invalid JSON – Malformed payloads trigger a
WSErrorResponsewithWSErrorCode.INVALID_JSON. - Schema validation errors – Pydantic validation failures are caught and returned as structured error responses.
- Unexpected exceptions – Any unhandled runtime exceptions are wrapped in the standard error format before transmission.
Configuring WebSocket Timeouts in OpenEnv
OpenEnv implements two independent timeout mechanisms that operate at different granularity levels. Both are configurable through server-side settings or per-action parameters.
Session Idle Timeout
The session idle timeout controls how long a WebSocket connection can remain inactive before automatic termination. This setting is defined in ConcurrencyConfig.session_timeout within src/openenv/core/env_server/types.py.
The reaper loop runs inside EnvServer._idle_session_reaper (approximately lines 480-495 in http_server.py). It monitors the last_activity_at timestamp of each SessionInfo object:
# In http_server.py
self._session_idle_timeout_s = self._concurrency_config.session_timeout
...
if now - info.last_activity_at > timeout:
# session idle too long → close it
Setting session_timeout to None disables automatic cleanup, though this is not recommended for production deployments.
MCP Tool-Call Timeout
The MCP tool-call timeout restricts execution duration for individual tool invocations within an MCP-enabled environment. This default ceiling is defined by the constant MCP_TOOL_CALL_TIMEOUT in src/openenv/core/env_server/mcp_environment.py.
In MCPEnvironment._handle_call_tool, the server applies the timeout using this logic:
# In mcp_environment.py
timeout = timeout_s if timeout_s is not None else MCP_TOOL_CALL_TIMEOUT
...
except subprocess.TimeoutExpired:
raise ToolError(
f"Tool '{action.tool_name}' timed out after {timeout} seconds",
code=WSErrorCode.TIMEOUT,
)
The default value is 30 seconds, though individual actions can override this by including a timeout_s field in the step payload.
How Timeouts Interact
Understanding the distinction between these timeouts prevents confusion during debugging:
- Idle timeout terminates the entire WebSocket session and destroys the environment instance. Clients must establish a new connection to continue.
- Tool-call timeout aborts only the specific tool execution. The session remains alive, allowing clients to retry the action or send other messages without reconnecting.
Practical Implementation Examples
Client-Side WebSocket Interaction
This example demonstrates proper error handling and timeout override when interacting with the OpenEnv server:
import json
import asyncio
import websockets
async def run():
async with websockets.connect("ws://localhost:8000/ws") as ws:
# 1️⃣ Reset the environment
await ws.send(json.dumps({"type": "reset", "data": {}}))
print(await ws.receive()) # → {"type":"observation",...}
# 2️⃣ Step with a custom timeout (overrides default 30s)
step_msg = {
"type": "step",
"data": {"action": {"code": "print('hi')"}, "timeout": 5}
}
await ws.send(json.dumps(step_msg))
print(await ws.receive()) # ← may contain an error if >5s
# 3️⃣ Query the state
await ws.send(json.dumps({"type": "state"}))
print(await ws.receive())
# 4️⃣ Close the session cleanly
await ws.send(json.dumps({"type": "close"}))
asyncio.run(run())
If the client remains silent longer than the configured session_timeout, the server closes the socket and subsequent receive() calls raise websockets.exceptions.ConnectionClosed.
Server-Side Timeout Configuration
When launching the environment server, adjust timeouts through the ConcurrencyConfig:
from openenv.core.env_server import ConcurrencyConfig, create_fastapi_app
config = ConcurrencyConfig(
max_concurrent_envs=4,
session_timeout=30.0 # 30s idle timeout
)
app = create_fastapi_app(
env=my_env,
action_cls=MyAction,
observation_cls=MyObs,
max_concurrent_envs=4,
concurrency_config=config,
)
Summary
- Error Handling: OpenEnv wraps all WebSocket errors in the
WSErrorResponsemodel defined intypes.py, ensuring consistent JSON error formats without exposing internal tracebacks. - Session Timeout: Configure idle session duration via
ConcurrencyConfig.session_timeoutinhttp_server.py; the_idle_session_reapertask enforces this limit automatically. - Tool Timeout: Control individual action execution through
MCP_TOOL_CALL_TIMEOUT(default 30s) inmcp_environment.py, overridable per-action via thetimeoutfield. - Architecture: The server separates transport-level session management from execution-level tool timeouts, allowing granular control over resource constraints.
Frequently Asked Questions
How does OpenEnv handle malformed JSON in WebSocket messages?
When the server receives invalid JSON, it catches the parsing exception and returns a WSErrorResponse with WSErrorCode.INVALID_JSON. This prevents the connection from closing unexpectedly and allows the client to correct the payload format without reconnecting.
What happens when a tool execution exceeds the MCP timeout?
If a tool call exceeds the MCP_TOOL_CALL_TIMEOUT (or the per-action override), the server raises a ToolError with code WSErrorCode.TIMEOUT. The WebSocket session remains open, and the client receives a structured error response indicating which tool timed out and after how many seconds.
Can I disable the session idle timeout entirely?
Yes, by setting session_timeout to None in the ConcurrencyConfig when calling create_fastapi_app. However, this is generally discouraged for production use as it allows orphaned sessions to consume resources indefinitely. The reaper loop in http_server.py skips sessions without a timeout configured.
Where is the WebSocket endpoint defined in the OpenEnv source code?
The /ws endpoint handler is implemented in src/openenv/core/env_server/http_server.py, specifically within the EnvServer class. This file also contains the _idle_session_reaper method (lines ~480-495) that manages session lifecycle based on the last_activity_at timestamps tracked in SessionInfo objects.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →