Webview CSS not applied in code-server behind subpath reverse proxy (mode picker giant ✓, labels concatenated)

Open 💬 0 comments Opened Jun 15, 2026 by imoes

Summary

When running the Claude Code extension inside code-server behind an nginx reverse proxy that strips a subpath prefix (e.g. location ~ ^/ide/<uid>/ → rewritten to /), the webview stylesheet (webview/index.css) is never applied. The result is a completely unstyled mode-picker popup: a giant ✓ checkmark fills the entire popup, and label + description text are concatenated without spacing.

Environment

  • code-server 4.123.0 (VS Code 1.96.x / Insider build)
  • Claude Code extension 2.1.177 (linux-x64)
  • nginx reverse proxy: location ~ ^/ide/<uid>/rewrite ^/ide/[^/]+/(.*)$ /$1 break + proxy_pass http://cs-container:8080
  • Browsers: Firefox, Chrome (same result)

Root Cause

The extension's getHtmlForWebview() generates:

<link href="${e.asWebviewUri(index.css)}" rel="stylesheet">

asWebviewUri() returns a vscode-resource:// (or https://<uuid>.vscode-resource.vscode-cdn.net/) URL. In a standard VS Code desktop or direct code-server setup, the webview service worker (registered at /stable-.../static/out/vs/workbench/contrib/webview/browser/pre/service-worker.js) intercepts this fetch and resolves the resource from the extension directory.

In a subpath-proxied code-server setup:

  1. The service worker registers with Service-Worker-Allowed: /ide/<uid>/ scope but its own path is at /ide/<uid>/stable-.../static/…/pre/service-worker.js
  2. Webview iframes are served at /ide/<uid>/stable-…/static/out/… — the SW scope nominally covers them
  3. However the SW's resource-interception guard checks requestUrl.protocol === 'https:' — when the user accesses via plain http: (or the resource URL doesn't match resourceBaseAuthority), the SW silently skips the fetch
  4. Without SW interception, the vscode-resource:// URL has no HTTP handler → the <link> request either 404s or is blocked by CSP, so CSS is never applied
  5. Even if style-src is broadened (https:, 'unsafe-inline'), the SW cache still serves stale resources after a reload — Ctrl+Shift+R bypasses the HTTP cache but not the service worker cache

Reproduction

  1. Run code-server in Docker with --auth none, no published host port
  2. Front it with nginx:

``nginx
location ~ ^/ide/(?<uid>[^/]+)/ {
rewrite ^/ide/[^/]+/(.*)$ /$1 break;
proxy_pass http://cs-container:8080;
add_header Service-Worker-Allowed /ide/$uid/ always;
}
``

  1. Open code-server, install Claude Code 2.1.177, open the Claude panel
  2. Click the mode button → popup shows giant ✓, labels like "Ask before editsClaude will ask..."

Workaround

We patch extension.js in the container entrypoint to replace the <link> tag with an inline <style> block — the file is read at render time via require("fs").readFileSync:

// Before (in getHtmlForWebview template literal):
<link href="${l}" rel="stylesheet">

// After:
<style>${(()=>{try{return require("fs").readFileSync(c.fsPath,"utf8")}catch(_){return ""}})()}</style>

This sidesteps the service worker, CSP host-matching, resource URL scheme, and caching entirely. The CSP already permits 'unsafe-inline' for styles, so the rules apply immediately. The mode picker renders correctly after this patch.

Proposed Fix

In getHtmlForWebview() (the method that returns the main webview HTML), inline the CSS content directly instead of using a <link> tag:

// Replace:
const cssUri = webview.asWebviewUri(vscode.Uri.joinPath(this.extensionUri, 'webview', 'index.css'));
// ...
<link href="${cssUri}" rel="stylesheet">

// With:
import * as fs from 'fs';
const cssPath = vscode.Uri.joinPath(this.extensionUri, 'webview', 'index.css').fsPath;
const cssContent = fs.readFileSync(cssPath, 'utf8');
// ...
<style>${cssContent}</style>

This makes the extension independent of the webview service worker for stylesheet delivery — which is robust across all hosting environments (desktop VS Code, code-server direct, code-server behind subpath proxy).

The file is ~380 KB, which is an acceptable inline payload given it is already part of the extension bundle. Alternatively the CSS could be minified or split so only critical mode-picker styles are inlined.

References

  • coder/code-server#3410 — related: webview resources fail in some setups
  • coder/code-server#2106 — service worker scope issues behind subpath proxy

View original on GitHub ↗