Skip to content

Conversation

@whoiskatrin
Copy link
Collaborator

@whoiskatrin whoiskatrin commented Dec 19, 2025

This PR adds full PTY (pseudo-terminal) support to the Sandbox SDK, enabling interactive terminal sessions with proper terminal emulation.

Summary

  • Add PTY support for interactive terminal sessions with xterm-256color emulation
  • Support both WebSocket (real-time) and HTTP (polling) transports for PTY I/O
  • Include dimension validation, structured exit codes, and graceful disconnect handling

Features
New sandbox.pty namespace:

  • create(options) - Create a new PTY session with configurable terminal size, command, cwd, and env
  • attach(sessionId) - Attach PTY to an existing session (inherits cwd/env)
  • getById(id) - Reconnect to an existing PTY session
  • list() - List all active PTY sessions
  • getInfo(id) - Get PTY metadata
  • resize(id, cols, rows) - Resize terminal dimensions
  • write(id, data) - Send input to PTY
  • kill(id, signal?) - Terminate PTY with optional signal

PTY Handle API:

  • write(data) - Send input to the terminal
  • resize(cols, rows) - Resize terminal window
  • kill(signal?) - Terminate with optional signal
  • onData(callback) - Subscribe to terminal output
  • onExit(callback) - Handle process exit
  • exited - Promise that resolves with exit code
  • Async iteration support for scripting use cases

Container Runtime:

  • PtyManager - Manages PTY lifecycle using Bun.Terminal API
  • Dimension validation (1-1000 for both cols and rows)
  • Structured exit codes with signal mapping (SIGINT, SIGTERM, SIGKILL, etc.)
  • Configurable disconnect timeout for orphaned sessions
  • Session attachment for cwd/env inheritance

Example Usage

// Create PTY with custom dimensions
const pty = await sandbox.pty.create({ cols: 120, rows: 40 });
// Subscribe to output
pty.onData((data) => terminal.write(data));
// Send commands
pty.write('cd /workspace && ls -la\n');
// Handle exit
pty.onExit((code) => console.log(`Exited with code ${code}`));
// Or use async iteration
for await (const data of pty) {
  process.stdout.write(data);
}

@changeset-bot
Copy link

changeset-bot bot commented Dec 19, 2025

🦋 Changeset detected

Latest commit: 5c7de26

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 Minor

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: 5c7de26

@github-actions
Copy link
Contributor

github-actions bot commented Dec 19, 2025

🐳 Docker Images Published

Default:

FROM cloudflare/sandbox:0.0.0-pr-310-0ae4905

With Python:

FROM cloudflare/sandbox:0.0.0-pr-310-0ae4905-python

With OpenCode:

FROM cloudflare/sandbox:0.0.0-pr-310-0ae4905-opencode

Version: 0.0.0-pr-310-0ae4905

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-0ae4905 /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]

Download via GitHub CLI:

gh run download 20973719172 -n sandbox-binary

Extract from Docker:

docker run --rm cloudflare/sandbox:0.0.0-pr-310-0ae4905 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 <[email protected]>
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.

Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

This PR adds comprehensive PTY support with a well-designed architecture that addresses most critical issues from previous reviews. Error handling has improved significantly - terminal operations now wrap exceptions and return structured results. However, two critical issues remain that could cause production reliability problems.

Critical Issues

1. SSE stream listeners continue firing after enqueue failures

packages/sandbox-container/src/handlers/pty-handler.ts:361-390, 393-424 - When controller.enqueue() throws (client disconnected), the error is caught and logged but the listener keeps firing. Every subsequent PTY data event triggers another caught exception, filling logs and wasting CPU.

Fix: On enqueue error, call unsubData() and unsubExit() immediately inside the catch blocks to stop further callbacks.

2. WebSocket double-failure scenario loses user feedback

packages/sandbox-container/src/handlers/ws-adapter.ts:107-129, 133-161 - When PTY write/resize fails AND the error notification cannot be sent over WebSocket (dead connection), users receive no feedback. The operation promise never resolves/rejects, leaving users unable to distinguish between slow operations and failed operations.

This violates the core principle: users must always know when operations fail. Consider adding request/response pattern with explicit IDs for PTY operations, or document this limitation clearly in the fire-and-forget JSDoc.

Important Issues

3. Missing critical test coverage

The primary use case - WebSocket PTY integration - has zero E2E test coverage. The collaborative-terminal example shows this is the intended production pattern, yet tests only cover HTTP fallback. Also missing:

  • Disconnect timeout behavior (resource leak risk)
  • Bun.Terminal unavailability error handling
  • Session attachment conflict unit test

See the pr-test-analyzer agent report for detailed severity ratings. At minimum, add WebSocket PTY E2E test before merge.

Positive Changes

  • terminal.write() and resize() now wrapped in try-catch with structured error returns
  • proc.exited has both .then() and .catch() handlers
  • Listeners properly cleaned up in cancel() callback (SSE streams)
  • No any types - proper BunTerminal interface defined
  • Error messages are clear and actionable

Verdict

Fix SSE listener cleanup (critical #1) and either fix or clearly document the WebSocket double-failure scenario (critical #2). Add at minimum the WebSocket PTY E2E test. The architectural foundation is solid, but these reliability issues could create difficult-to-debug production problems.

@whoiskatrin
Copy link
Collaborator Author

this has been also tested by @sdan in his work, he has positive feedback

whoiskatrin and others added 2 commits January 5, 2026 15:33
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Copy link
Contributor

@claude claude bot left a comment

Choose a reason for hiding this comment

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

Claude Code Review

All previous error handling issues are fixed. The write/resize error handling, disconnect scenarios, and callback wrapping are properly implemented.

However, the specialized review agents identified several issues in disconnect/cleanup paths where errors are logged but not acted upon:

Critical:

  1. WebSocketAdapter error notification failure (ws-adapter.ts:117-127, 147-159): When PTY write/resize fails AND the error notification to client fails, the error is only logged. The client never learns the operation failed and continues interacting with a broken PTY. Should close the WebSocket to force reconnect.

  2. PtyManager disconnect timer lacks context (pty-manager.ts:395-406): When disconnect timeout fires and kill() fails, error is logged but lacks critical context (PTY state, process PID, whether PTY is now in zombie state). The catch block doesn't check kill()'s return value - if kill() returns {success: false}, that's swallowed.

High Priority:
3. SSE stream listener leak (pty-handler.ts:361-390): When SSE enqueue fails (client disconnected), the error is caught and logged but listeners remain registered. PTY continues generating output that's discarded, wasting resources. Should unsubscribe immediately.

  1. WebSocket disconnect cleanup (ws-transport.ts:604-635): Stream controller errors during disconnect are logged at debug level. Should be warning level to surface potential race conditions.

Test Coverage Gaps:
The test suite covers happy paths well but misses critical edge cases:

  • Empty write operations (severity 9)
  • Rapid resize operations (severity 8)
  • Disconnect timeout behavior (severity 8)
  • Control character handling (Ctrl+C, Ctrl+Z) (severity 8)
  • WebSocket vs HTTP transport differences (severity 9)
  • Async iterator edge cases (severity 8)

The agents have detailed analysis in their output. Recommend addressing the critical error handling issues before merge, and adding the high-severity test gaps soon after.

Verdict: Address critical error notification and disconnect handling issues, then merge. Test gaps can follow.

connectionId: ws.data.connectionId
}
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Critical: When error notification fails, client will never know the operation failed. They'll continue interacting with a broken PTY. Close the WebSocket to force reconnect:

if (!errorSent) {
  this.logger.error(...);
  try {
    ws.close(1011, 'Failed to send error notification');
  } catch (closeError) {
    this.logger.error('Failed to close WebSocket', closeError, { connectionId: ws.data.connectionId });
  }
}

error instanceof Error ? error : new Error(errorMessage),
{ ptyId }
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

High: When enqueue fails, listeners remain registered and PTY continues generating discarded output. Unsubscribe immediately to prevent resource waste:

if (isExpectedDisconnect) {
  logger.debug('SSE stream closed, cleaning up listeners', { ptyId });
} else {
  logger.error('SSE stream enqueue failed, cleaning up listeners', ...);
}
unsubData?.();
unsubExit?.();

agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 5, 2026
Add comprehensive documentation for the new PTY feature that enables
interactive terminal sessions in sandboxes.

New documentation includes:
- API reference for sandbox.pty namespace and PTY handle methods
- How-to guide for interactive terminals with practical examples
- Integration examples with xterm.js for browser-based terminals
- Coverage of WebSocket and HTTP transport modes
- Terminal dimension configuration and control character handling
- Multi-user collaborative terminal patterns

Updated main sandbox documentation to feature PTY capabilities in
overview tabs and feature cards.

Related to cloudflare/sandbox-sdk#310
Copy link
Member

@ghostwriternr ghostwriternr left a comment

Choose a reason for hiding this comment

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

The overall implementation is v solid and the example you've built is easily my favourite example we've shipped. The API - especially the choice to use the pty namespace under the sandbox class is neat. I wish we did that for all methods, maybe we for v1 💁

100% of my comments in this PR are mainly around the architecture of the system itself (specifically transports), cleaning up confusing semantics (attach to session), and eliminating some deadcode/redundancies that got accumulated over the course of what is clearly a non-trivial change. Aside from the transport recommendations, all the other recommendation will primarily reduce the amount of code we ship (which is one of my fav takeaways from this article Peter shared yesterday) and keep the implementation leaner/cleaner.

Comment on lines 77 to 96

/**
* Send PTY input (WebSocket only, no-op for HTTP)
*/
sendPtyInput(ptyId: string, data: string): void;

/**
* Send PTY resize (WebSocket only, no-op for HTTP)
*/
sendPtyResize(ptyId: string, cols: number, rows: number): void;

/**
* Register PTY data listener (WebSocket only)
*/
onPtyData(ptyId: string, callback: (data: string) => void): () => void;

/**
* Register PTY exit listener (WebSocket only)
*/
onPtyExit(ptyId: string, callback: (exitCode: number) => void): () => void;
Copy link
Member

Choose a reason for hiding this comment

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

These methods probably don't belong here. The more I thought about it, it feels like a very natural fit for the PTY support is to use websockets because of the continuous bidirectional communication on every input (keystroke) and continuous output - and this is something you've already done (looking mainly at the changes here and to ws-transport.ts (and the unimplemented stubs on the http transport).

But the above is also exactly why the way to achieve this is to not add PTY-specific components to the transport layer itself, which serves a more foundational purpose that both the HTTP & WS transports implement. But instead, the PTY feature should unconditionally use the websocket transport and use the standard interface provided by the transport instead.

I'd imagine we could instead perhaps have (these are just illustrative, could think about whether the existing methods are useful too):

abstract sendMessage(message: object): void;
abstract onStreamEvent(
  streamId: string,
  event: string,
  callback: (data: string) => void
): () => void;

and have both transports implement this. Remove all pty specific code like listeners from the ws-transport implementation and use a more standardised streamEventListeners perhaps, and pty-client could possibly then do things like:

transport.sendMessage({ type: 'pty_input', ptyId, data });
transport.onStreamEvent(ptyId, 'pty_data', callback);

Right now, this sort of very tight unidirectional coupling is defeating the purpose of having a common transport.

Copy link
Member

Choose a reason for hiding this comment

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

Other files in this PR that are related to the same comment: ws-transport.ts, http-transport.ts, base-transport.ts, pty-client.ts

Copy link
Member

Choose a reason for hiding this comment

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

And rough pseudocode for how pty-client (?) changes as a result could be:

private ptyTransport: WebSocketTransport | null = null;

private async getPtyTransport(): Promise<WebSocketTransport> {
  if (!this.ptyTransport) {
    this.ptyTransport = new WebSocketTransport({...});
    await this.ptyTransport.connect();
  }
  return this.ptyTransport;
}

async create(options?: CreatePtyOptions): Promise<Pty> {
  const transport = await this.getPtyTransport();
  // ... create PTY, pass transport to PtyHandle
}

* @example
* const pty = await client.attach('session_123', { cols: 100, rows: 30 });
*/
async attach(sessionId: string, options?: AttachPtyOptions): Promise<Pty> {
Copy link
Member

Choose a reason for hiding this comment

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

We seem to have a lot of code around the implementation for this attach feature, and while I initially got excited by the idea of it, the more I looked at this, the less clear I was on the exact purpose of this one. When I read "attach", I assumed it implies an ongoing relationship between PTY and session, but in reality the PTY gets its own bash process that diverges immediately after creation. And this part feels fair - because our session implementation uses FIFO-based I/O and primarily designed for a non-interactive use-case, wherein PTY uses TTY-based I/O and is for interactive use-cases. But what this means, is that attach is primarily just a way to avoid the user having to write

const pty = await sandbox.pty.create({
  cwd: '/workspace/project',
  env: { NODE_ENV: 'development' }
});

which is something we entirely support already. And furthermore, when you look at the fact that we currently use getInitialCwd and getInitialEnv, it's also not the latest state of the session but is only the initial state - making this entire attach feature even less enticing/useful.

And the existence of this feature has also lead to implementation of exclusive control, which doesn't make sense because the PTY and the session are divergent anyway so there is no reason to block one or the other.

So my personal recommendation is to remove the attach feature entirely unless there are strong reasons to keep it. We could get rid of the attach() method, handleAttach(), getSessionInfo, sessionToPty mapping, hasActivePty(), getBySessionId(), checkPtyExclusiveControl(), the attach route, AttachPtyOptions type, and PTY_EXCLUSIVE_CONTROL error code. And any other related code too.

}
try {
// Handle Ctrl+C (ETX, 0x03) - send SIGINT to process group
if (data === '\x03') {
Copy link
Member

Choose a reason for hiding this comment

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

This comment is maybe relevant to the other signals too. When I tested using the example app you have deployed to your account, it looks like Ctrl+C/Z send signals to the shell process only, but not to the foreground process group. So for instance, if you run sleep 100 and press Ctrl+C/Z, nothing happens because the SIGINT gets sent to bash but is ignored and thus the sleep never gets SIGINT.

Can you remind me why we need this special handling here? Naively, it feels like this method could just be:

write(id: string, data: string): { success: boolean; error?: string } {
  const session = this.sessions.get(id);
  if (!session) return { success: false, error: 'PTY not found' };
  if (session.state !== 'running') return { success: false, error: 'PTY has exited' };

  session.terminal.write(data);
  return { success: true };
}

But there's certainly a reason why this was added that I'm missing in my testing.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's due to this bug in bun.terminal oven-sh/bun#25834

return () => session.exitListeners.delete(callback);
}

startDisconnectTimer(id: string): void {
Copy link
Member

Choose a reason for hiding this comment

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

Looks like these methods are never used (start and cancel), but are only being used in tests. We could remove them? And by extension, I noticed the disconnectTimer/disconnectTimeout option defined elsewhere seems to be unused as well. And fwiw - following the general pattern in this SDK - I lean towards not having any automatic timeouts at all in the SDK. We could provide an optional option if users ask, but for simplicity, I'd just remove it because it's guaranteed to surprise users if we implement this properly and PTY just disconnects after 30 seconds of inactivity by default and users have to read source code/docs to understand how to not have that happen. Instead, we already expose kill to users so if they need timeouts, they can achieve it using very little code anyway and they have much more control over the process.

* from repeatedly attempting to send to a dead connection.
* Also tracked per-connection for cleanup when connection closes.
*/
registerPtyListener(ws: ServerWebSocket<WSData>, ptyId: string): () => void {
Copy link
Member

Choose a reason for hiding this comment

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

This method seems unused as well and only used by tests - and is not exposed to developers either. We can delete this and all related code if not needed?

// Create WebSocket adapter with the router for control plane multiplexing
const wsAdapter = new WebSocketAdapter(router, logger);
const ptyManager = container.get('ptyManager');
const wsAdapter = new WebSocketAdapter(router, ptyManager, logger);
Copy link
Member

Choose a reason for hiding this comment

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

+related to ws cleanup

Comment on lines +10 to +18
/**
* Minimal interface for Bun.Terminal (introduced in Bun v1.3.5+)
* Defined locally since it's only used in the container runtime.
* @types/bun doesn't include this yet, so we define it here.
*/
interface BunTerminal {
write(data: string): void;
resize(cols: number, rows: number): void;
}
Copy link
Member

Choose a reason for hiding this comment

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

Here, and maybe a few other places, I noticed we're re-defining some bun types ourselves, but possibly the much cleaner approach would be to use "@types/bun": "^1.3.5" which should have all these types and use them directly instead.

Comment on lines 422 to 459
/**
* Resize a PTY (synchronous - waits for completion)
*
* @param id - PTY ID
* @param cols - Number of columns
* @param rows - Number of rows
*/
async resize(id: string, cols: number, rows: number): Promise<void> {
const response = await this.doFetch(`/api/pty/${id}/resize`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cols, rows })
});

// Use handleResponse to properly parse ErrorResponse on failure
await this.handleResponse<{ success: boolean }>(response);

this.logSuccess('PTY resized', `${id} -> ${cols}x${rows}`);
}

/**
* Send input to a PTY (synchronous - waits for completion)
*
* @param id - PTY ID
* @param data - Input data to send
*/
async write(id: string, data: string): Promise<void> {
const response = await this.doFetch(`/api/pty/${id}/input`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data })
});

// Use handleResponse to properly parse ErrorResponse on failure
await this.handleResponse<{ success: boolean }>(response);

this.logSuccess('PTY input sent', id);
}
Copy link
Member

Choose a reason for hiding this comment

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

Any reason why this exists here, as well as in PtyHandle (where it does seem more appropriate)? We can probably delete these?

}
}

killAll(): void {
Copy link
Member

Choose a reason for hiding this comment

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

nit: this should probably be called from the cleanup function in server.ts?


// Session Errors (409)
SESSION_ALREADY_EXISTS: 'SESSION_ALREADY_EXISTS',
PTY_EXCLUSIVE_CONTROL: 'PTY_EXCLUSIVE_CONTROL',
Copy link
Member

Choose a reason for hiding this comment

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

Nit: This gets eliminated based on the comment re: attach feature (regardless of whether attach itself gets removed, this is bound to go I think). But perhaps the right set of error codes might be PTY_NOT_FOUND, PTY_WRITE_FAILED, PTY_ALREADY_EXITED that gets used appropriately. Right now, this PR does not use our overall error handling system and instead uses vanilla Error which doesn't surface up in useful manner.

agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 8, 2026
Document new PTY support for interactive terminal sessions, including:

- API reference for sandbox.pty namespace with methods for creating, managing, and interacting with PTY sessions
- Complete PTY handle API documentation covering write(), resize(), kill(), onData(), onExit(), and async iteration
- How-to guide for creating interactive terminal sessions with practical examples
- Integration examples with xterm.js for browser-based terminals
- Real-time collaborative terminal implementation patterns
- WebSocket transport details and configuration
- Terminal emulation capabilities and exit code handling

Related to cloudflare/sandbox-sdk#310
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 8, 2026
Add comprehensive documentation for the new PTY API that enables interactive
terminal sessions in Sandbox SDK. This documentation covers:

- Complete API reference for sandbox.pty namespace (create, attach, getById, list, etc.)
- PTY handle methods (write, resize, kill, onData, onExit, async iteration)
- Practical how-to guide for building interactive terminals with xterm.js
- WebSocket integration examples for real-time terminal communication
- Multi-user collaborative terminal patterns
- Interactive application examples (vim, Python REPL, htop)
- Signal handling and error management
- Session attachment for environment inheritance

The PTY feature enables developers to build terminal UIs, collaborative coding
environments, and interactive shell applications using Cloudflare Sandboxes.

Related to cloudflare/sandbox-sdk#310
agents-git-bot bot pushed a commit to cloudflare/cloudflare-docs that referenced this pull request Jan 8, 2026
Add comprehensive how-to guide covering:
- When to use PTY vs exec() for different use cases
- Integration with xterm.js for web-based terminals
- WebSocket handler implementation for real-time I/O
- Terminal control sequences (ANSI escape codes)
- Running interactive applications (Python REPL, vim, etc.)
- Collaborative terminal pattern with multiple users
- Session reconnection for disconnect/reconnect scenarios
- Best practices for PTY session management

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.
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
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.

2 participants