Browser pane: service-worker registration fails — script fetch never leaves the app

Status Open
Reported on v2.1.227
Maintainer reply None cached
Activity 3 comments · opened Aug 13, 2026

Environment: Claude Code desktop 2.1.227, macOS 25.6.0. Pane UA: Claude/1.28929.0 Chrome/148.0.7778.280 Electron/42.7.0.

Symptom: In any Browser-pane tab, navigator.serviceWorker.register() rejects with
TypeError: Failed to register a ServiceWorker for scope ('http://localhost:8931/') with script ('http://localhost:8931/sw.js'): An unknown error occurred when fetching the script.

Minimal repro: Serve a one-line sw.js with python3 -m http.server 8931, open the page in the pane, call navigator.serviceWorker.register('/sw.js').

Evidence it dies pre-network, pane-side:

  • Page-context fetch('/sw.js') → 200 text/javascript; the server access log shows that request.
  • The log shows zero requests for either register() attempt (plain and cache-busted with updateViaCache: 'none') — the SW script fetch never reaches the server.
  • Fresh origin (no prior state), top-level frame (window.top === window), secure context, CacheStorage and storage.estimate() healthy.
  • Reproduced on three localhost servers/ports (vite ×2, python ×1). Real Chrome on the same machine registers the same files first try.
  • Regression: MSW-based dev workflows registered fine in the pane through ~2026-08-02.
  • Persists across a full app relaunch (Cmd+Q → reopen, still 2.1.227): identical failure on re-test.

Impact: Any app needing a service worker in the pane (MSW mock-mode dev, PWAs) cannot boot; verification falls back to a real browser.

Guess: The pane session's request interception (origin-approval/browsing-policy layer) doesn't handle the service-worker script fetch context, so the browser-process fetch is dropped.

View original on GitHub ↗

3 Comments

Cmacu · 15 days ago

Additional data point: the failure is ServiceWorker-specific, not a general worker-script-fetch drop.

Ran a controlled repro in the Browser pane against a local python3 -m http.server, creating five worker types from the same origin in a single page:

| Worker | Script source | Result |
|---|---|---|
| new Worker('/dedicated.js') | HTTP | ✅ ran |
| new Worker('/m.js', { type: 'module' }) | HTTP | ✅ ran |
| new SharedWorker('/shared.js') | HTTP | ✅ ran |
| new Worker(blob: URL) | blob | ✅ ran |
| navigator.serviceWorker.register('/sw.js') | HTTP | ❌ TypeError: ...An unknown error occurred when fetching the script. |

Dedicated and shared workers fetch their scripts over HTTP from the same origin and start fine in the pane; only the ServiceWorker script fetch is dropped. That narrows the "request-interception layer doesn't handle the worker-script fetch context" hypothesis specifically to the ServiceWorker fetch path — dedicated/shared worker script fetches are handled correctly.

Practical implication: apps whose local persistence uses a DedicatedWorker/SharedWorker (e.g. OPFS SQLite) are unaffected and boot in the pane; only ServiceWorker-dependent flows (MSW mock mode, PWA install/offline) break. Same environment notes as the original report (secure context, top-level frame; real Chrome registers the same files first try).

<details>
<summary>Repro (serve this dir with <code>python3 -m http.server 8931 --bind 127.0.0.1</code>, open <code>/index.html</code> in the pane)</summary>

Sibling scripts: dedicated.js = self.postMessage('dedicated-worker-ran'); module-worker.js = same; shared.js = self.onconnect = (e) => e.ports[0].postMessage('shared-worker-ran'); sw.js = self.addEventListener('install', () => self.skipWaiting()).

<!doctype html><meta charset=utf8><title>worker repro</title>
<pre id=out>starting...
</pre>
<script>
const out = document.getElementById('out');
const log = (m) => { out.textContent += m + "\n"; };
async function run() {
  // 1. Classic dedicated worker (HTTP-fetched script)
  try {
    const w = new Worker('/dedicated.js');
    w.onmessage = (e) => log('DEDICATED classic: ' + e.data);
    w.onerror = (e) => log('DEDICATED classic ERROR: ' + (e.message||e.type));
  } catch (e) { log('DEDICATED classic THREW: ' + e); }

  // 2. Module dedicated worker (HTTP-fetched)
  try {
    const w = new Worker('/module-worker.js', { type: 'module' });
    w.onmessage = (e) => log('DEDICATED module: ' + e.data);
    w.onerror = (e) => log('DEDICATED module ERROR: ' + (e.message||e.type));
  } catch (e) { log('DEDICATED module THREW: ' + e); }

  // 3. Shared worker (HTTP-fetched script)
  try {
    const sw = new SharedWorker('/shared.js', { name: 'repro' });
    sw.port.onmessage = (e) => log('SHARED: ' + e.data);
    sw.onerror = (e) => log('SHARED ERROR: ' + (e.message||e.type));
    sw.port.start();
  } catch (e) { log('SHARED THREW: ' + e); }

  // 4. Blob-URL dedicated worker (NO http fetch - control)
  try {
    const blob = new Blob(["self.postMessage('blob-worker-ran')"], {type:'text/javascript'});
    const w = new Worker(URL.createObjectURL(blob));
    w.onmessage = (e) => log('BLOB dedicated: ' + e.data);
    w.onerror = (e) => log('BLOB dedicated ERROR: ' + (e.message||e.type));
  } catch (e) { log('BLOB dedicated THREW: ' + e); }

  // 5. Service worker (the issue's case)
  try {
    const reg = await navigator.serviceWorker.register('/sw.js');
    log('SERVICEWORKER: registered ok, scope ' + reg.scope);
  } catch (e) { log('SERVICEWORKER ERROR: ' + e); }
}
run();
</script>

</details>

Cmacu · 15 days ago

**Follow-up: a second worker-context fetch drop — SharedWorker subresource fetches also fail (not just ServiceWorker script fetches).**

Chasing why a dev app wouldn't boot in the pane, I isolated another case in the same family. The pane drops HTTP module/subresource fetches initiated from a SharedWorker context, while the identical fetch from a dedicated worker succeeds.

Isolation matrix (all same-origin, local python3 -m http.server, run in the pane):

| Case | Pane |
|---|---|
| Dedicated worker imports an HTTP module (static or dynamic import()) | ✅ works |
| Self-contained SharedWorker, no imports (onconnect + MessagePort) | ✅ works |
| SharedWorker imports an HTTP module (static top-level OR dynamic import()) | ❌ fails — opaque onerror; dynamic form logs Failed to fetch dynamically imported module: http://127.0.0.1:8932/dep.js |
| ServiceWorker register('/sw.js') | ❌ fails (this issue) |

So it isn't dynamic-vs-static and it isn't "workers can't fetch" — a dedicated worker fetches HTTP modules fine. It's specifically the SharedWorker context whose subresource fetch is dropped, mirroring the ServiceWorker script-fetch drop. Real Chrome on the same machine loads all of these first try.

Minimal repro:

dep.js                -> export const ok = 'dep-loaded-over-http';
shared-static.mjs     -> import { ok } from './dep.js';
                         self.onconnect = (e) => e.ports[0].postMessage('static:' + ok);
<script>
  // FAILS in pane (onerror), works in Chrome:
  const s = new SharedWorker('/shared-static.mjs', { type: 'module', name: 's' });
  s.onerror = () => console.log('SharedWorker onerror (subresource fetch dropped)');
  s.port.onmessage = (e) => console.log('OK:', e.data);
  s.port.start();

  // WORKS in pane (control): same import from a dedicated worker
  new Worker(URL.createObjectURL(new Blob(
    ["import('"+location.origin+"/dep.js').then(m=>self.postMessage('ded:'+m.ok))"],
    { type: 'text/javascript' })), { type: 'module' })
    .onmessage = (e) => console.log(e.data);
</script>

Real-world impact: apps whose local-persistence/DB layer runs a SharedWorker that loads modules over HTTP (common in Vite/webpack dev mode — e.g. an OPFS-SQLite worker) get an opaque SharedWorker error and never finish booting in the pane; the DB never opens. Production builds that fully bundle the SharedWorker (no HTTP subresource import) are unaffected, which is why it presents as dev-only and works in a real browser. Suggests the fix should cover the SharedWorker fetch context alongside the ServiceWorker one.

netopolit · 15 days ago
Environment: Claude Code desktop 2.1.227, macOS 25.6.0. Pane UA: Claude/1.28929.0 Chrome/148.0.7778.280 Electron/42.7.0. Symptom: In any Browser-pane tab, navigator.serviceWorker.register() rejects with TypeError: Failed to register a ServiceWorker for scope ('http://localhost:8931/') with script ('http://localhost:8931/sw.js'): An unknown error occurred when fetching the script. Minimal repro: Serve a one-line sw.js with python3 -m http.server 8931, open the page in the pane, call navigator.serviceWorker.register('/sw.js'). Evidence it dies pre-network, pane-side: Page-context fetch('/sw.js') → 200 text/javascript; the server access log shows that request. The log shows zero requests for either register() attempt (plain and cache-busted with updateViaCache: 'none') — the SW script fetch never reaches the server. Fresh origin (no prior state), top-level frame (window.top === window), secure context, CacheStorage and storage.estimate() healthy. Reproduced on three localhost servers/ports (vite ×2, python ×1). Real Chrome on the same machine registers the same files first try. Regression: MSW-based dev workflows registered fine in the pane through ~2026-08-02. Persists across a full app relaunch (Cmd+Q → reopen, still 2.1.227): identical failure on re-test. Impact: Any app needing a service worker in the pane (MSW mock-mode dev, PWAs) cannot boot; verification falls back to a real browser. Guess: The pane session's request interception (origin-approval/browsing-policy layer) doesn't handle the service-worker script fetch context, so the browser-process fetch is dropped.

Forgot to mention that this is a regression. It was introduced sometime in the last two weeks or so.