Skip to content

Port main updates to next#811

Open
aron-cf wants to merge 1 commit into
nextfrom
upstream-next
Open

Port main updates to next#811
aron-cf wants to merge 1 commit into
nextfrom
upstream-next

Conversation

@aron-cf

@aron-cf aron-cf commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

This pull request brings the relevant main branch changes into next while preserving the next execution model.

Integrated upstream pull requests:

The release tooling changes from main are included so prerelease and stable artifact generation use the same orchestration path. The sandbox container labels API is included with its SDK option, storage behavior, and tests. The bridge warm pool changes are included with the configured instance ceiling, eager refill, bounded parallel scale-up, and custom span tracing. The bridge example dependency update and backup/restore benchmark are also included.

The stable version package commits from #783, #785, and #806 were not applied directly because next should keep its prerelease versioning flow. #778 was also not carried forward because the enableDefaultSession option is deprecated and must not exist on next. The resulting branch has no enableDefaultSession references.

Some main changes were not clean cherry-picks because next has moved to the unified process API. The bridge /exec streaming route from #786 was adapted from the old stream: true callback shape to read from the SandboxProcess stdout and stderr streams. The bridge persist and hydrate routes were also adjusted to use .output({ encoding: 'utf8' }) when checking command exit codes.

This also includes a small SDK fix found while validating the minimal example: withSandboxOperationContext() now preserves decorated sandbox.exec() promises so helper methods like .output(), .text(), .json(), and .kill() survive the wrapper. A regression test covers that behavior.

Validation run:

npm run build -w @cloudflare/sandbox
npm test -w @cloudflare/sandbox -- get-sandbox.test.ts sandbox.test.ts
npm test -w @cloudflare/sandbox-bridge -- --run src/__tests__/warm-pool.test.ts src/__tests__/tracing.test.ts
npm run typecheck -w @cloudflare/sandbox-bridge
npm run typecheck -w @cloudflare/sandbox
npm run check

Open in Devin Review

@changeset-bot

changeset-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 75941ed

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 2 potential issues.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 enableDefaultSession removed from SandboxOptions — public API surface change

The enableDefaultSession field was removed from SandboxOptions in packages/shared/src/types.ts (old lines 783-792). This is a public API change that removes a documented option. The corresponding changelog entry was also removed from packages/sandbox/CHANGELOG.md (old line 56). If any consumers of @cloudflare/sandbox are passing enableDefaultSession: false, their code will get a TypeScript error after upgrading. This appears intentional but is not mentioned in any changeset description in this PR.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +785 to +844
`${apiPrefix}/sandbox/:id/pty`,
traced('pty', async (c) => {
// 1. Require WebSocket upgrade
const upgrade = c.req.header('Upgrade');
if (!upgrade || upgrade.toLowerCase() !== 'websocket') {
return errorJson('WebSocket upgrade required', 'invalid_request', 400);
}

const sandbox = getSandbox(getSandboxNs(c.env), c.get('containerUUID'));

// 2. Parse terminal options from query params
const colsParam = c.req.query('cols');
const rowsParam = c.req.query('rows');
const shell = c.req.query('shell');
const cwd = c.req.query('cwd');
const terminalId =
c.req.header('Terminal-Id') ??
c.req.query('terminalId') ??
c.req.query('terminal');

const cols = colsParam ? Number(colsParam) : 80;
const rows = rowsParam ? Number(rowsParam) : 24;

if (Number.isNaN(cols) || Number.isNaN(rows)) {
return errorJson(
'cols and rows must be valid numbers',
'invalid_request',
400
);
}
const sandbox = getSandbox(getSandboxNs(c.env), c.get('containerUUID'));

const terminalOptions: TerminalOptions = {};
const connectOptions: TerminalConnectOptions = { cols, rows };
if (shell) {
terminalOptions.shell = shell;
}
if (cwd) {
terminalOptions.cwd = cwd;
}
if (!terminalId) {
return errorJson(
'terminalId query parameter or Terminal-Id header required',
'invalid_request',
400
);
}
// 2. Parse PtyOptions from query params
const colsParam = c.req.query('cols');
const rowsParam = c.req.query('rows');
const shell = c.req.query('shell');
const sessionId = c.req.header('Session-Id') || c.req.query('session');

const validatedId = validateSessionId(terminalId);
if (!validatedId)
return errorJson('Invalid terminal ID format', 'invalid_request', 400);
terminalOptions.id = validatedId;
const cols = colsParam ? Number(colsParam) : 80;
const rows = rowsParam ? Number(rowsParam) : 24;

try {
return await sandbox
.terminal(terminalOptions)
.connect(c.req.raw, connectOptions);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return errorJson(`terminal failed: ${msg}`, 'exec_transport_error', 502);
}
});
if (Number.isNaN(cols) || Number.isNaN(rows)) {
return errorJson(
'cols and rows must be valid numbers',
'invalid_request',
400
);
}

annotate(c, 'pty.cols', cols);
annotate(c, 'pty.rows', rows);

const opts: PtyOptions = { cols, rows };
if (shell) {
opts.shell = shell;
annotate(c, 'pty.shell', shell);
}

try {
if (sessionId) {
const validatedId = validateSessionId(sessionId);
if (!validatedId)
return errorJson(
'Invalid session ID format',
'invalid_request',
400
);
annotate(c, 'session.id', validatedId);
const sess = await sandbox.getSession(validatedId);
return await sess.terminal(c.req.raw, opts);
}
return await sandbox.terminal(c.req.raw, opts);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return errorJson(
`terminal failed: ${msg}`,
'exec_transport_error',
502
);
}
})
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔍 PTY route handler changed from terminal ID to session-based dispatch

The PTY WebSocket handler at packages/sandbox/src/bridge/routes.ts:785-844 was significantly refactored. Previously it required a Terminal-Id header or terminalId/terminal query parameter and called sandbox.terminal(terminalOptions).connect(request, connectOptions). Now it uses an optional Session-Id header or session query parameter and calls either sandbox.terminal(request, opts) or sess.terminal(request, opts). The cwd query parameter is no longer parsed from the request. The BridgeSandbox type was updated to match at lines 62-67, changing terminal() from returning a SandboxTerminal to accepting a Request and returning Promise<Response>. This is a breaking change for any bridge API consumers that relied on the Terminal-Id header, terminalId/terminal query parameters, or the cwd query parameter for PTY connections.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new

pkg-pr-new Bot commented Jul 6, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@cloudflare/sandbox@811

commit: 75941ed

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🐳 Docker Images Published

Variant Image
Default cloudflare/sandbox:0.0.0-pr-811-75941ed0
Python cloudflare/sandbox:0.0.0-pr-811-75941ed0-python
OpenCode cloudflare/sandbox:0.0.0-pr-811-75941ed0-opencode
Musl cloudflare/sandbox:0.0.0-pr-811-75941ed0-musl

Usage:

FROM cloudflare/sandbox:0.0.0-pr-811-75941ed0

Version: 0.0.0-pr-811-75941ed0


📦 Standalone Binary

For arbitrary Dockerfiles:

COPY --from=cloudflare/sandbox:0.0.0-pr-811-75941ed0 /container-server/sandbox /sandbox
ENTRYPOINT ["/sandbox"]

Download via GitHub CLI:

gh run download 28815463957 -n sandbox-binary

Extract from Docker:

docker run --rm cloudflare/sandbox:0.0.0-pr-811-75941ed0 cat /container-server/sandbox > sandbox && chmod +x sandbox

Bring over the relevant main changes while preserving next-only
execution semantics and excluding deprecated default-session flags.
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.

1 participant