DEV Community

Cover image for Notes from a 2 AM On-Call: Why Harper's WASM Module Silently Failed in Our Self-Hosted VSCode Server
Clara Dupont
Clara Dupont

Posted on

Notes from a 2 AM On-Call: Why Harper's WASM Module Silently Failed in Our Self-Hosted VSCode Server

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Check if the language server binary has execute permissions
  2. Verify the extension is activated in workspace settings
  3. 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"
Enter fullscreen mode Exit fullscreen mode

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-src whitelists 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";
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)