Skip to content

fix opencode terminal connection#353

Closed
TimoWilhelm wants to merge 7 commits intocloudflare:mainfrom
TimoWilhelm:tiwi/fix-opencode-terminal
Closed

fix opencode terminal connection#353
TimoWilhelm wants to merge 7 commits intocloudflare:mainfrom
TimoWilhelm:tiwi/fix-opencode-terminal

Conversation

@TimoWilhelm
Copy link
Copy Markdown

@TimoWilhelm TimoWilhelm commented Jan 14, 2026

Fix WebSocket Proxy + CSP headers for terminal support in opencode web ui

Also fixes the home path to fix file listing

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 14, 2026

⚠️ No Changeset found

Latest commit: e4bb1f1

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@TimoWilhelm TimoWilhelm force-pushed the tiwi/fix-opencode-terminal branch from bde51ac to 89afb56 Compare January 15, 2026 08:27
@scuffi scuffi added the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@scuffi
Copy link
Copy Markdown
Contributor

scuffi commented Mar 9, 2026

Hey @TimoWilhelm,

Thanks for the PR, looks good & safe to merge if you can resolve the conflict?

Thanks!

@whoiskatrin
Copy link
Copy Markdown
Collaborator

/bonk fix the conflicts

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR @TimoWilhelm — the WebSocket proxy fix and CSP relaxation for Ghostty WASM are solid improvements. The Dockerfile HOME fix is a good catch too.

I have one correctness issue with the CSP response construction pattern and a few smaller observations.

Also noting that no tests were added for the two new code paths (WebSocket upgrade detection and CSP rewriting). These would be straightforward to add to the existing proxyToOpencode test suite.

Comment thread packages/sandbox/src/opencode/opencode.ts
const csp = response.headers.get('Content-Security-Policy');
if (csp) {
const newCsp = csp
.replace(/connect-src\s+'self'/, "connect-src 'self' data:")
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.

Nit: These regexes only match connect-src 'self' and script-src 'self' exactly — if the upstream CSP ever includes additional sources (e.g., connect-src 'self' https://example.com), the replacement still works but only because it matches the prefix. However, if the directive were connect-src 'none' or used a different order, it would silently not match and the terminal would break without any visible error.

Might be worth a comment noting this is fragile / tied to the specific CSP that OpenCode emits today.

Comment thread packages/sandbox/src/opencode/opencode.ts Outdated
@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

fatal: could not read Username for 'https://github.com': No such device or address

github run

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

@whoiskatrin Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
Co-authored-by: ask-bonk[bot] <249159057+ask-bonk[bot]@users.noreply.github.com>
@sandy-bonk sandy-bonk Bot removed the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@sandy-bonk
Copy link
Copy Markdown
Contributor

sandy-bonk Bot commented Mar 9, 2026

New commits pushed -- ok-to-test label removed. A maintainer must re-review and re-apply the label to run the full CI pipeline.

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR — the WebSocket proxy via sandbox.fetch(switchPort(...)), CSP relaxation for Ghostty WASM, and the Dockerfile HOME fix all look correct and well-motivated.

I see the previous review's suggestions (explicit status/statusText, .toLowerCase() on Upgrade header) are already incorporated in the code. A few remaining items below.

Merge conflict: The PR is currently in a conflicting state and needs a rebase.

Changeset: Since this changes the public SDK behavior (proxyToOpencode now handles WebSocket upgrades and rewrites CSP headers), a patch changeset for @cloudflare/sandbox should be added.

Test coverage: The two new code paths (WebSocket upgrade detection and CSP header rewriting) have no test coverage. The existing proxyToOpencode test suite would be a natural place to add these — a WebSocket request test asserting sandbox.fetch is called instead of containerFetch, and a test for CSP header modification. Note that createMockSandboxForProxy currently only provides containerFetch but the WebSocket path calls sandbox.fetch, so the mock will need updating too. The @cloudflare/containers module (for switchPort) will also need to be mocked in this test file.

return sandbox.containerFetch(request, server.port);
const response = await sandbox.containerFetch(request, server.port);

// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
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.

Nit: The parenthetical "(skip for WebSocket responses)" is misleading here. WebSocket requests already return early at line 434, so this code is never reached for WebSocket requests. The comment reads as if there's skip logic here, but there isn't.

Suggested change
// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
// Modify CSP headers to allow Ghostty WASM terminal loading

@@ -299,17 +298,17 @@ describe('proxyToOpencode', () => {
} as unknown as Sandbox;
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.

This mock is now incomplete for the new code paths. The WebSocket branch calls sandbox.fetch(switchPort(...)), but this mock only provides containerFetch. Tests for the WebSocket path will need a fetch mock here.

Also, since the source file now imports switchPort from @cloudflare/containers, you'll need a vi.mock('@cloudflare/containers', ...) at the top of this test file (see tests/sandbox.test.ts for the pattern).

Suggested test cases to add:

  1. WebSocket upgrade request routes through sandbox.fetch (not containerFetch)
  2. Response with CSP header gets connect-src and script-src rewritten
  3. Response without CSP header is returned unchanged

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

Review submitted successfully. Here's a summary of what I flagged:

Review posted on PR #353 with 2 inline comments and a summary:

  1. Merge conflict — PR is in CONFLICTING state and needs a rebase before it can be merged.

  2. Missing changeset — The PR changes public SDK behavior (proxyToOpencode now handles WebSocket upgrades and rewrites CSP headers), so a patch changeset for @cloudflare/sandbox is needed.

  3. Misleading comment (inline at opencode.ts:456) — The "(skip for WebSocket responses)" parenthetical is inaccurate since WebSocket requests return early at line 434. Suggested rewording.

  4. Missing test coverage (inline at opencode.test.ts:298) — No tests for the two new code paths (WebSocket proxy and CSP rewriting). The mock createMockSandboxForProxy also needs a fetch method, and @cloudflare/containers needs to be mocked for switchPort. Suggested three specific test cases to add.

github run

@whoiskatrin
Copy link
Copy Markdown
Collaborator

/bonk fix the conflicts

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the fix — the WebSocket proxy via sandbox.fetch(switchPort(...)), CSP relaxation for Ghostty WASM, and the Dockerfile HOME fix all make sense.

The previous review's core suggestions (explicit status/statusText, .toLowerCase() on Upgrade header) have been incorporated, which is great. A few things remain unresolved:

Merge conflict — The PR is in CONFLICTING state and needs a rebase onto main.

Missing changeset — This changes public SDK behavior (proxyToOpencode now handles WebSocket upgrades and rewrites CSP headers), so a patch changeset for @cloudflare/sandbox is needed per the project's contribution guidelines. Note: the changeset should only reference @cloudflare/sandbox, not @cloudflare/sandbox-opencode-example.

Missing test coverage — The two new code paths (WebSocket upgrade detection and CSP header rewriting) have no tests. The existing proxyToOpencode test suite is the natural place. Suggested test cases:

  1. WebSocket upgrade request routes through sandbox.fetch (not containerFetch)
  2. Response with CSP header gets connect-src and script-src rewritten correctly
  3. Response without CSP header is returned unchanged

Note that createMockSandboxForProxy will need a fetch mock, and @cloudflare/containers will need to be mocked in this test file (see tests/sandbox.test.ts for the switchPort mock pattern).

I've left an inline comment on the remaining code-level issue.

return sandbox.containerFetch(request, server.port);
const response = await sandbox.containerFetch(request, server.port);

// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
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.

This parenthetical is misleading. WebSocket requests already return early at line 434, so this code is never reached for WebSocket responses. The comment reads as if there's skip logic here, but there isn't.

Suggested change
// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
// Modify CSP headers to allow Ghostty WASM terminal loading

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

Review submitted successfully. Here's a summary of the feedback:

Request changes on PR #353 with 1 inline comment:

  1. Merge conflict — PR is in CONFLICTING state and needs a rebase onto main.

  2. Missing changeset — The PR changes public SDK behavior (proxyToOpencode now handles WebSocket upgrades and rewrites CSP headers), so a patch changeset for @cloudflare/sandbox is needed. Should not reference @cloudflare/sandbox-opencode-example.

  3. Missing test coverage — No tests for the two new code paths (WebSocket proxy and CSP rewriting). Suggested three specific test cases and noted that createMockSandboxForProxy needs a fetch mock and @cloudflare/containers needs to be mocked.

  4. Misleading comment (inline at opencode.ts:456) — The "(skip for WebSocket responses)" parenthetical is inaccurate since WebSocket requests return early at line 434. Suggested removing it.

github run

@whoiskatrin
Copy link
Copy Markdown
Collaborator

/bonk resolve the conflicts

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. The WebSocket proxy fix, CSP header rewriting, and Dockerfile HOME correction all look reasonable. A few items remain before this is mergeable.

Merge conflict: The PR is still in a conflicting state and needs a rebase onto main.

Missing changeset: proxyToOpencode is part of the public @cloudflare/sandbox API. Adding WebSocket handling and CSP rewriting is a behavioral change that needs a patch changeset. Per project guidelines, only reference @cloudflare/sandbox (not the example package).

Missing test coverage: The two new code paths have no tests. See inline comments for details.

Otherwise the implementation looks solid — the WebSocket path correctly mirrors the pattern in request-handler.ts, status/statusText are passed explicitly (avoiding the spread bug from the earlier review), and the .toLowerCase() on the Upgrade header is correct.

return sandbox.containerFetch(request, server.port);
const response = await sandbox.containerFetch(request, server.port);

// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
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.

Previous reviews already flagged this: the "(skip for WebSocket responses)" parenthetical is inaccurate. WebSocket requests return early at line 434, so this code is never reached for WebSocket requests. The comment reads as if there's skip logic here when there isn't.

Suggested change
// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
// Modify CSP headers to allow Ghostty WASM terminal loading

Comment on lines +460 to +461
.replace(/connect-src\s+'self'/, "connect-src 'self' data:")
.replace(/script-src\s+'self'/, "script-src 'self' 'wasm-unsafe-eval'");
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.

These regexes are fragile — they only match the exact pattern connect-src 'self' / script-src 'self' (with flexible whitespace between the directive name and 'self'). If the upstream OpenCode CSP ever adds additional sources or changes ordering, the replacements silently won't match and the terminal will break with no visible error.

Not necessarily a blocker, but a short comment documenting this fragility and what CSP shape is expected would help future maintainers. Something like:

// These replacements assume OpenCode's current CSP format where connect-src and
// script-src contain only 'self'. If OpenCode's CSP changes, these may need updating.

@@ -299,17 +298,17 @@ describe('proxyToOpencode', () => {
} as unknown as Sandbox;
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.

This mock is now incomplete for the new code paths:

  1. The WebSocket branch calls sandbox.fetch(switchPort(...)), but this mock only provides containerFetch. A fetch mock is needed.
  2. The source file now imports switchPort from @cloudflare/containers, which needs to be mocked in this test file (see tests/sandbox.test.ts lines 55-59 for the pattern).

Suggested test cases to add:

  • WebSocket upgrade: Request with Upgrade: websocket header routes through sandbox.fetch (not containerFetch), and switchPort is called with server.port.
  • CSP rewriting: Response with a Content-Security-Policy header gets connect-src and script-src rewritten with the expected additions (data: and 'wasm-unsafe-eval').
  • No CSP: Response without a CSP header is returned unchanged (body, status, headers all pass through).

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 9, 2026

Review posted successfully. Here's what I flagged on PR #353:

Summary: The implementation is sound — WebSocket proxy mirrors the pattern in request-handler.ts, the status/statusText spread bug was already fixed, and .toLowerCase() on the Upgrade header is correct.

3 items to address before merge:

  1. Merge conflict — PR is in CONFLICTING state and needs a rebase onto main.

  2. Missing changesetproxyToOpencode is public API; a patch changeset for @cloudflare/sandbox is needed.

  3. Missing test coverage — No tests for the WebSocket upgrade path or CSP rewriting. The createMockSandboxForProxy mock needs a fetch method, and @cloudflare/containers needs to be mocked.

2 inline code comments:

  • opencode.ts:456 — The "(skip for WebSocket responses)" parenthetical is misleading since WebSocket requests return early at line 434. Suggested simpler wording.
  • opencode.ts:460-461 — CSP regex replacements are fragile and tied to OpenCode's current CSP format. Suggested adding a comment documenting this assumption.
  • opencode.test.ts:298 — Mock is incomplete for new code paths; suggested 3 specific test cases to add.

github run

@whoiskatrin whoiskatrin added the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Mar 9, 2026

Open in StackBlitz

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

commit: e4bb1f1

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

📦 Preview Build

Version: 0.0.0-pr-353-e4bb1f1

Install the SDK preview:

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

🐳 Docker images were not rebuilt — no container changes detected. Use the latest release images from Docker Hub.

…minal

# Conflicts:
#	.changeset/many-deers-dance.md
@sandy-bonk sandy-bonk Bot removed the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@sandy-bonk
Copy link
Copy Markdown
Contributor

sandy-bonk Bot commented Mar 9, 2026

New commits pushed -- ok-to-test label removed. A maintainer must re-review and re-apply the label to run the full CI pipeline.

@whoiskatrin whoiskatrin added the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@sandy-bonk sandy-bonk Bot removed the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@sandy-bonk
Copy link
Copy Markdown
Contributor

sandy-bonk Bot commented Mar 9, 2026

New commits pushed -- ok-to-test label removed. A maintainer must re-review and re-apply the label to run the full CI pipeline.

@whoiskatrin whoiskatrin added the ok-to-test Maintainer-approved: run full CI on fork PR label Mar 9, 2026
@scuffi
Copy link
Copy Markdown
Contributor

scuffi commented Mar 10, 2026

/bonk add a changeset, test coverage and update the openCode example in the repo from these changes

Copy link
Copy Markdown
Contributor

@ask-bonk ask-bonk Bot left a comment

Choose a reason for hiding this comment

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

Thanks for the PR — the WebSocket proxy, CSP rewriting, and Dockerfile HOME fix all look correct. The core implementation is solid: WebSocket handling mirrors request-handler.ts, status/statusText are passed explicitly (avoiding the spread-doesn't-copy-getters issue), and the Upgrade header comparison is properly case-insensitive.

A few items remain from prior reviews that should be addressed before merge:

  1. Misleading comment on line 482 — The (skip for WebSocket responses) parenthetical is inaccurate since WebSocket requests return early at line 460. Previous reviews suggested this fix but it hasn't been applied yet.

  2. Missing changesetproxyToOpencode is public API; adding WebSocket handling and CSP rewriting is a behavioral change that needs a patch changeset for @cloudflare/sandbox only (not the example package).

  3. Missing test coverage — The two new code paths (WebSocket upgrade and CSP rewriting) have no tests. The mock at line 341 also needs updating: it lacks a fetch method (needed by the WebSocket path), and @cloudflare/containers needs to be mocked for switchPort (see tests/sandbox.test.ts:10-16 for the pattern). Three test cases would give good coverage:

    • WebSocket upgrade request routes through sandbox.fetch + switchPort, not containerFetch
    • Response with CSP header gets connect-src and script-src rewritten
    • Response without CSP header passes through unchanged

return proxyToOpencodeServer(request, sandbox, server);
const response = await proxyToOpencodeServer(request, sandbox, server);

// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
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.

This parenthetical is inaccurate — WebSocket requests already returned at line 460, so this code is never reached for WebSocket responses. There's no skip logic here.

Suggested change
// Modify CSP headers to allow Ghostty WASM loading (skip for WebSocket responses)
// Modify CSP headers to allow Ghostty WASM terminal loading

Comment on lines +485 to +487
const newCsp = csp
.replace(/connect-src\s+'self'/, "connect-src 'self' data:")
.replace(/script-src\s+'self'/, "script-src 'self' 'wasm-unsafe-eval'");
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.

These regexes only match when connect-src / script-src contain exactly 'self' (possibly with flexible whitespace). If OpenCode ever adds additional sources to these directives or changes ordering, the replacements silently won't match and the terminal will break with no visible error.

A short comment documenting this assumption would help future maintainers:

Suggested change
const newCsp = csp
.replace(/connect-src\s+'self'/, "connect-src 'self' data:")
.replace(/script-src\s+'self'/, "script-src 'self' 'wasm-unsafe-eval'");
// These replacements assume OpenCode's current CSP format where connect-src and
// script-src directives start with only 'self'. If OpenCode's CSP changes, update these.
const newCsp = csp
.replace(/connect-src\s+'self'/, "connect-src 'self' data:")
.replace(/script-src\s+'self'/, "script-src 'self' 'wasm-unsafe-eval'");

@@ -342,17 +341,17 @@ describe('proxyToOpencode', () => {
} as unknown as Sandbox;
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.

This mock is incomplete for the new code paths:

  1. The WebSocket branch calls sandbox.fetch(switchPort(...)), but this mock only provides containerFetch. A fetch mock is needed.
  2. The source now imports switchPort from @cloudflare/containers, which must be mocked in this test file. See tests/sandbox.test.ts:10-16 for the pattern.

Suggested test cases:

  • WebSocket upgrade: Request with Upgrade: websocket routes through sandbox.fetch (not containerFetch)
  • CSP rewriting: Response with Content-Security-Policy header gets connect-src and script-src rewritten
  • No CSP: Response without CSP header passes through unchanged (body, status, headers intact)

@ask-bonk
Copy link
Copy Markdown
Contributor

ask-bonk Bot commented Mar 10, 2026

Review posted successfully on PR #353 with 3 inline comments and a summary. Here's what I flagged:

Implementation assessment: The core changes are solid — WebSocket proxy mirrors request-handler.ts, explicit status/statusText avoids the spread-doesn't-copy-getters bug, and Upgrade header comparison is case-insensitive.

3 items to address before merge:

  1. Misleading comment (opencode.ts:482) — The "(skip for WebSocket responses)" parenthetical is inaccurate since WebSocket requests return early at line 460. Suggested simpler wording.

  2. Fragile CSP regexes (opencode.ts:485-487) — The replacements only match a specific CSP format. Suggested adding a comment documenting this assumption for future maintainers.

  3. Missing test coverage (opencode.test.ts:341) — The mock is incomplete for the new code paths (no fetch method, no @cloudflare/containers mock). Suggested three test cases: WebSocket upgrade routing, CSP rewriting, and no-CSP passthrough.

Additionally noted the need for a patch changeset for @cloudflare/sandbox.

github run

@scuffi
Copy link
Copy Markdown
Contributor

scuffi commented Mar 10, 2026

@TimoWilhelm bonk seems to be having some trouble, instead, could you please push a changeset, some test coverage and update the OpenCode example now this method is async?

Everything else looks good to merge after those changes!

Thanks for the PR :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ok-to-test Maintainer-approved: run full CI on fork PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants