Notes from a 2 AM On-Call: Why Harper's WASM Module Silently Failed in Our Self-Hosted VSCode Server
The Failure Symptom
[Extension Host] Error loading webview: Failed to execute 'postMessage' on 'Worker'
[Harper] WASM module instantiation failed: CompileError
Browser console: Refused to create a worker from 'blob:' (CSP violation)
Grammar checking just stopped. No crashes, no obvious errors in the extension logs. Users opened files, typed paragraphs with obvious typos, and Harper stayed silent. The language server was running, CPU showed activity, but zero suggestions appeared.
Rollback to the previous VSCode extension version didn't help. The self-hosted code-server instance ran fine for weeks before this.
The Red Herring
Standard Harper documentation focuses on local desktop VSCode installations. When it breaks, the typical checklist is:
- Check if the language server binary has execute permissions
- Verify the extension is activated in workspace settings
- Confirm file types are registered in
settings.json
All three passed. The language server process was alive (ps aux | grep harper-ls showed the daemon running). File associations were correct. Restarting the extension host changed nothing.
The real issue? Self-hosted code-server deployments reverse-proxy through Traefik or Nginx, and Harper's WASM worker threads require specific Content-Security-Policy headers that most default reverse proxy configs explicitly block.
The Root Cause & Runnable Fix
Harper compiles its core grammar engine to WebAssembly for performance. The VSCode extension spawns Web Workers to run the WASM module off the main thread. When your reverse proxy sends a restrictive CSP header without worker-src 'self' blob: and script-src 'unsafe-eval', the browser silently refuses to instantiate the worker.
Here's the Traefik dynamic config that fixed it:
http:
middlewares:
code-server-headers:
headers:
customResponseHeaders:
Content-Security-Policy: "default-src 'self'; script-src 'self' 'unsafe-eval'; worker-src 'self' blob:; connect-src 'self' https://b-lost.com/v1 wss:;"
routers:
code-server:
rule: "Host(`code.homelab.internal`)"
service: code-server
middlewares:
- code-server-headers
tls:
certResolver: tailscale
services:
code-server:
loadBalancer:
servers:
- url: "http://localhost:8080"
The critical additions:
-
worker-src 'self' blob:allows WASM worker instantiation from blob URLs -
script-src 'unsafe-eval'permits WebAssembly compilation (required by most WASM modules) -
connect-srcwhitelists WebSocket connections for the language server protocol
If you're running Nginx instead:
location / {
proxy_pass http://localhost:8080;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; worker-src 'self' blob:; connect-src 'self' wss:;" always;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
Testing the Fix
After applying the Traefik config:
# Reload Traefik dynamic configuration
docker exec traefik kill -SIGHUP 1
# Open browser dev console, reload code-server
# Should see:
# [Harper] WASM module loaded successfully
# [Harper] Language server connected
Open any markdown or text file with intentional typos. Harper suggestions appeared within 200ms. CPU usage for the harper-ls process stayed under 2% during idle grammar checks.
Invariant Rule
For any self-hosted web IDE or extension that uses WebAssembly workers (Harper, Monaco language services, browser-based compilers), explicitly audit your reverse proxy's CSP headers before deploying to production.
Document the exact worker-src and script-src directives in your homelab runbook. When a "working" extension suddenly goes silent after a proxy config change, check CSP violations in the browser console first—before diving into application logs or reinstalling extensions.
The failure mode is silent by design (browsers suppress worker creation errors to prevent fingerprinting), which makes it invisible to server-side monitoring. Always test extensions that worked locally in your actual reverse-proxy environment before rolling out to your team.
Top comments (0)