Skip to content

Add PTY terminal passthrough for browser clients#310

Merged
ghostwriternr merged 63 commits intomainfrom
pty-support
Feb 6, 2026
Merged

Add PTY terminal passthrough for browser clients#310
ghostwriternr merged 63 commits intomainfrom
pty-support

Conversation

@whoiskatrin
Copy link
Collaborator

@whoiskatrin whoiskatrin commented Dec 19, 2025

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.
  • Multiple terminals per sandbox — each session can have its own terminal with isolated working directory and environment, so users can run separate shells side-by-side in the same container.
  • @cloudflare/sandbox/xtermSandboxAddon for xterm.js with automatic reconnection (exponential backoff + jitter), buffered output replay, and resize forwarding.

Usage

// Worker: proxy WebSocket to container terminal
return sandbox.terminal(request, { cols: 80, rows: 24 });

// Multiple isolated terminals in the same sandbox
const dev = await sandbox.getSession('dev');
return dev.terminal(request);
// Browser: xterm.js addon
import { SandboxAddon } from '@cloudflare/sandbox/xterm';

const addon = new SandboxAddon({
  getWebSocketUrl: ({ sandboxId, origin }) =>
    `${origin}/ws/terminal?id=${sandboxId}`,
  onStateChange: (state, error) => updateUI(state),
});
terminal.loadAddon(addon);
addon.connect({ sandboxId: 'my-sandbox' });

@changeset-bot
Copy link

changeset-bot bot commented Dec 19, 2025

🦋 Changeset detected

Latest commit: 9f6d732

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/sandbox Patch

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

claude[bot]

This comment was marked as outdated.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 19, 2025

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/sandbox-sdk/@cloudflare/sandbox@310

commit: bfd0fc2

@github-actions
Copy link
Contributor

github-actions bot commented Dec 19, 2025

🐳 Docker Images Published

Default:

FROM cloudflare/sandbox:0.0.0-pr-310-a012aaf

With Python:

FROM cloudflare/sandbox:0.0.0-pr-310-a012aaf-python

With OpenCode:

FROM cloudflare/sandbox:0.0.0-pr-310-a012aaf-opencode

Version: 0.0.0-pr-310-a012aaf

Use the -python variant if you need Python code execution, or -opencode for the variant with OpenCode AI coding agent pre-installed.


📦 Standalone Binary

For 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-binary

Extract from Docker:

docker run --rm cloudflare/sandbox:0.0.0-pr-310-a012aaf cat /container-server/sandbox > sandbox && chmod +x sandbox

agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Dec 19, 2025
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
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Dec 19, 2025
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
claude[bot]

This comment was marked as outdated.

agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Dec 19, 2025
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>
claude[bot]

This comment was marked as outdated.

agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Dec 19, 2025
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
claude[bot]

This comment was marked as outdated.

claude[bot]

This comment was marked as outdated.

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.
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 9, 2026
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.
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 13, 2026
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 20, 2026
github-actions[bot]

This comment was marked as outdated.

whoiskatrin pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 26, 2026
@ghostwriternr ghostwriternr changed the title add PTY support Add PTY terminal passthrough for browser clients Feb 6, 2026
github-actions[bot]

This comment was marked as outdated.

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.
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@ghostwriternr ghostwriternr merged commit 3c03587 into main Feb 6, 2026
8 checks passed
@ghostwriternr ghostwriternr deleted the pty-support branch February 6, 2026 14:41
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message could be more specific. Consider: 'terminal() requires WebSocket upgrade header: expected "websocket", got "..."}'

@github-actions github-actions bot mentioned this pull request Feb 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants