Add PTY terminal passthrough for browser clients#310
Conversation
🦋 Changeset detectedLatest commit: 9f6d732 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
commit: |
🐳 Docker Images PublishedDefault: FROM cloudflare/sandbox:0.0.0-pr-310-a012aafWith Python: FROM cloudflare/sandbox:0.0.0-pr-310-a012aaf-pythonWith OpenCode: FROM cloudflare/sandbox:0.0.0-pr-310-a012aaf-opencodeVersion: Use the 📦 Standalone BinaryFor arbitrary Dockerfiles: COPY --from=cloudflare/sandbox:0.0.0-pr-310-a012aaf /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]Download via GitHub CLI: gh run download 21754460919 -n sandbox-binaryExtract from Docker: docker run --rm cloudflare/sandbox:0.0.0-pr-310-a012aaf cat /container-server/sandbox > sandbox && chmod +x sandbox |
Documents the new sandbox.pty namespace introduced in cloudflare/sandbox-sdk#310: - create() - Create new PTY session - attach() - Attach PTY to existing session - getById() - Reconnect to existing PTY - list() - List all PTY sessions Includes comprehensive examples for: - Interactive shell sessions - Running terminal applications (vim, REPLs) - Terminal multiplexing - PTY handle methods (write, resize, kill, onData, onExit) - Async iteration for scripting workflows Synced from: cloudflare/sandbox-sdk#310
Add comprehensive API documentation for the new PTY feature introduced in cloudflare/sandbox-sdk#310, which enables interactive terminal sessions in sandboxes. Documentation includes: - Complete API reference for pty.create(), pty.attach(), pty.getById(), pty.list() - PTY handle methods: write(), resize(), kill(), onData(), onExit(), close() - Async iteration support for scripting use cases - Use case examples: interactive shells, language REPLs, automated sessions - Transport considerations (HTTP vs WebSocket) - Updated API index with PTY card Related to sandbox-sdk PR #310
Sync documentation for cloudflare/sandbox-sdk#310 - Add PTY API reference with complete method documentation - Add interactive terminals how-to guide with practical examples - Update API index to include PTY card PTY support enables interactive terminal sessions with proper control character handling, terminal resizing, and bidirectional communication. Supports use cases like interactive shells, text editors, and TUIs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Document the new PTY functionality added in sandbox-sdk PR #310. PTY enables interactive terminal sessions for running shells, text editors (vim/nano), and other terminal-based applications. New documentation includes: - Complete API reference for sandbox.pty namespace - All PTY handle methods (write, resize, kill, onData, onExit, close) - Usage examples for interactive shells, vim/nano, and scripted workflows - Integration with xterm.js for web-based terminals - Async iteration support for scripting Related to: cloudflare/sandbox-sdk#310
The WebSocket connect tests were flaky because they didn't wait for the echo server to be ready after /api/init. Added a helper function that retries WebSocket connection with backoff before running tests.
The 404 issues were caused by stale container instances, not route registration problems. Reverting the debug logging changes: - Remove INFO-level route logging from router - Remove logRegisteredRoutes() method - Revert PR-specific Docker cache scope (not needed)
Use quoted heredoc and printf to safely handle PR description content that may contain backticks, code blocks, or other shell-sensitive characters. Pass PR body via environment variable to prevent shell interpretation during prompt construction.
Use environment variable to pass prompt to opencode run, avoiding shell interpretation of special characters like parentheses, backticks, and dollar signs that appear in PR descriptions with code examples. The prompt is stored in OPENCODE_PROMPT env var which GitHub Actions sets safely, then referenced with double quotes in the shell command.
Keep environment variable approach for passing prompt to opencode run to avoid shell escaping issues with special characters in PR descriptions.
The recent env var changes in 7da85c0 introduced Record<string, string | undefined> but missed updating getInitialEnv return type and getSessionInfo. Also aligns vite-plugin versions across examples.
bfd0fc2 to
f1dd2b8
Compare
Enables browser-based terminal UIs to connect to sandbox shells via WebSocket. The terminal() method proxies connections to the container's PTY endpoint with output buffering for replay on reconnect.
f1dd2b8 to
961caa2
Compare
There was a problem hiding this comment.
OpenCode Review
This PR adds comprehensive PTY terminal support for browser-based terminal UIs. The implementation is well-structured across the three-layer architecture, adding WebSocket passthrough from the SDK to container-side PTY management.
Architecture Assessment
Layer separation is clean: PTY handling is properly distributed - WebSocket routing in the server layer, business logic in SessionManager, and SDK integration via proxy methods. The use of Bun's Terminal API is appropriate for the container runtime.
Code Quality
Strong error handling: The PTY WebSocket handler has proper connection lifecycle management, graceful error responses, and cleanup on disconnection. Error propagation through status messages is well designed.
Memory management: ByteRingBuffer implementation for output buffering is solid - handles wrap-around correctly and provides proper capacity management for terminal history replay.
Implementation Concerns
Container dependency injection: Adding PtyWebSocketHandler to the DI container follows established patterns and integrates properly with SessionManager.
Session mutex interaction: PTY creation correctly uses the session mutex via getPty() in SessionManager, maintaining consistency with command execution serialization model described in the concurrency docs.
WebSocket connection discrimination: The server properly discriminates between control plane WebSockets (type: 'control') and PTY WebSockets (type: 'pty') in the unified handler.
Missing Elements
Browser E2E tests: The package.json shows test:e2e:browser using Playwright, but no browser test files are visible. For a terminal feature, automated browser testing would verify WebSocket connectivity, xterm.js integration, and terminal interaction flows.
Testing coverage: While unit tests would be expected for the ring buffer and PTY classes, no test files are visible for the new PTY functionality. Terminal features benefit from integration tests covering reconnection, output buffering, and resize events.
SDK Integration
getSandbox proxy pattern: The proxy approach in getSandbox() to intercept session-returning methods and enhance them with terminal() is clever. It preserves RPC behavior while adding client-side WebSocket capabilities.
Type safety: The PTY types in shared package are properly exported and used consistently across layers.
Looks good to merge once browser testing is in place to verify the complete terminal experience.
* Add tests and infrastructure for PTY terminal Unit tests for ring buffer, PTY spawning, and WebSocket handler. E2E tests for PTY workflow and browser terminal addon integration. Updates CI workflows and documentation for new test patterns. * Add collaborative terminal example and refine xterm addon (#376) * Refactor SandboxAddon to use connect(target) * Add collaborative terminal example Demonstrates the SandboxAddon connect() API with real-time room switching, presence tracking, and session isolation across multiple terminal rooms. Co-authored-by: Naresh <naresh@cloudflare.com> * Clear bash startup warning in container PTY Bash emits 'cannot set terminal process group' warnings in containers where the shell isn't a session leader. This clutters the first lines of terminal output for users. * Add build/** to turbo output cache React Router outputs to build/ instead of dist/, causing turbo to warn about missing outputs for the collaborative-terminal example. * Update collaborative-terminal README for new API The previous README documented the old PTY API (sandbox.pty.create() with JSON WebSocket protocol) that no longer exists. Updated to reflect the current implementation using session.terminal(request) with direct WebSocket passthrough and SandboxAddon for terminal integration. --------- Co-authored-by: katereznykova <kreznykova@cloudflare.com> --------- Co-authored-by: katereznykova <kreznykova@cloudflare.com>
There was a problem hiding this comment.
OpenCode Review
Strong implementation of PTY terminal passthrough functionality. The architecture cleanly separates concerns with WebSocket transport handled at the DO layer and PTY management in the container. The xterm.js addon provides solid client-side handling with proper reconnection.
Key Strengths
Layered Architecture: The separation between WebSocket transport (DO level) and PTY management (container level) is well designed. The proxyTerminal() function elegantly bridges the two layers.
Client Reliability: The SandboxAddon implements proper exponential backoff + jitter reconnection, buffered output replay, and handles both binary (terminal data) and JSON (control messages) protocols correctly.
Type Safety: New PtyOptions and PtyStatusMessage types are properly shared between layers and the export structure maintains clean separation between main SDK and xterm-specific functionality.
Areas for Improvement
Container Changes Missing from Review: The PR adds container-side PTY handling (pty-ws-handler.ts) but I don't see the core Pty class implementation or session manager integration. Are these changes in a separate file that should be included?
Buffer Management: The buffered output replay in SandboxAddon clears the terminal only if no buffer was received, but what happens with very large buffers that might exceed WebSocket message limits?
Error Propagation: Control message errors in the container (handleControl) are logged but only generic "Invalid control message" is sent to clients. More specific error types would help debugging.
Looks good to merge once container implementation is confirmed complete.
| this.handleControlMessage(msg); | ||
| } catch { | ||
| // Non-JSON string messages are silently ignored - protocol expects | ||
| // binary for terminal data and JSON for control messages only |
There was a problem hiding this comment.
Silent ignore of non-JSON strings could hide protocol issues. Consider logging malformed messages at debug level for troubleshooting.
| throw new Error('sessionId is required for terminal access'); | ||
| } | ||
|
|
||
| const upgradeHeader = request.headers.get('Upgrade'); |
There was a problem hiding this comment.
This error message could be more specific. Consider: 'terminal() requires WebSocket upgrade header: expected "websocket", got "..."}'
Summary
Enables browser-based terminal UIs to connect to sandbox shells via WebSocket. The
terminal()method proxies WebSocket connections to the container's PTY, with output buffering for replay on reconnect.What's included
sandbox.terminal(request)— proxy a WebSocket upgrade to a container PTY.@cloudflare/sandbox/xterm—SandboxAddonfor xterm.js with automatic reconnection (exponential backoff + jitter), buffered output replay, and resize forwarding.Usage