{"record":{"id":"3221535dd2349b63","repo":"GraphiteEditor/Graphite","slug":"precache-fetch-failed-for-entry-url-response","errorCode":null,"errorMessage":"Precache fetch failed for ${entry.url}: ${response.status}","messagePattern":"Precache fetch failed for (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/service-worker.js","lineNumber":62,"sourceCode":"\t\tconst cached = await cache.match(request);\n\t\tif (cached) return cached;\n\t\tthrow new Error(`Network request failed and no cache available for ${request.url}`);\n\t}\n}\n\n// ================\n// Lifecycle events\n// ================\n\nself.addEventListener(\"install\", (event) => {\n\tevent.waitUntil(\n\t\t(async () => {\n\t\t\t// Precache app shell assets\n\t\t\tconst cache = await caches.open(STATIC_CACHE_NAME);\n\t\t\tawait Promise.all(\n\t\t\t\tPRECACHE_MANIFEST.map(async (entry) => {\n\t\t\t\t\tconst response = await fetch(entry.url);\n\t\t\t\t\tif (!response.ok) throw new Error(`Precache fetch failed for ${entry.url}: ${response.status}`);\n\t\t\t\t\t// Strip the `redirected` flag which causes errors when served via respondWith\n\t\t\t\t\tconst cleaned = response.redirected\n\t\t\t\t\t\t? new Response(response.body, {\n\t\t\t\t\t\t\t\tstatus: response.status,\n\t\t\t\t\t\t\t\tstatusText: response.statusText,\n\t\t\t\t\t\t\t\theaders: response.headers,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: response;\n\t\t\t\t\tawait cache.put(entry.url, cleaned);\n\t\t\t\t}),\n\t\t\t);\n\n\t\t\t// Proactively cache the font catalog API\n\t\t\ttry {\n\t\t\t\tconst fontResponse = await fetch(FONT_LIST_API);\n\t\t\t\tif (fontResponse.ok) {\n\t\t\t\t\tconst fontCache = await caches.open(RUNTIME_FONTS);\n\t\t\t\t\tawait fontCache.put(FONT_LIST_API, fontResponse);","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/GraphiteEditor/Graphite/blob/c507b356453361e31638b8bff8f6d46b6da2961e/frontend/src/service-worker.js#L44-L80","documentation":"During service-worker install, every URL in PRECACHE_MANIFEST (JS, Wasm, index.html, favicons — placeholders injected by vite.config.ts at build time) is fetched; any non-ok response aborts the Promise.all, which rejects event.waitUntil and fails installation. The new service worker never activates, so clients stay on the previous version or end up with no worker at all.","triggerScenarios":"A manifest entry returns 404 — typically a hashed asset name from a build whose files were not all deployed; a host returning 401/403 for an asset; an HTTP-cached stale 404 for a previously missing file; index.html reached through a redirect chain that ends in an error status.","commonSituations":"Partial deploys where some dist/ artifacts (especially hashed files like the split -partN.wasm chunks referenced by the manifest) were not uploaded; serving from a subpath or wrong scope so relative manifest URLs resolve incorrectly; a CDN purge mid-deploy; stale browser HTTP cache during a service-worker update.","solutions":["Open the failing entry.url from the error message in a browser or curl and confirm the status — this pinpoints the missing asset","Rebuild and redeploy so PRECACHE_MANIFEST is generated from the exact asset set that ships (every hashed file present on the server)","If clients hold a stale manifest, bumping SERVICE_WORKER_CONTENT_HASH via a fresh deploy gives them a consistent worker+assets pair","Fetch with { cache: \"reload\" } and/or retry transient failures once during install instead of failing the entire install"],"exampleFix":"// before\nconst response = await fetch(entry.url);\nif (!response.ok) throw new Error(`Precache fetch failed for ${entry.url}: ${response.status}`);\n\n// after: bypass stale HTTP cache and retry transient failures once\nasync function fetchEntry(url) {\n\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\ttry {\n\t\t\tconst r = await fetch(url, { cache: \"reload\" });\n\t\t\tif (r.ok) return r;\n\t\t} catch { /* retry */ }\n\t}\n\treturn fetch(url, { cache: \"reload\" });\n}\nconst response = await fetchEntry(entry.url);\nif (!response.ok) throw new Error(`Precache fetch failed for ${entry.url}: ${response.status}`);","handlingStrategy":"retry","validationCode":"// Pre-deploy smoke check: every precache URL must return 200\nimport { readFileSync } from \"node:fs\";\nconst manifest = JSON.parse(readFileSync(\"dist/sw-manifest.json\", \"utf8\"));\nfor (const { url } of manifest) {\n\tconst res = await fetch(new URL(url, base).href);\n\tif (!res.ok) throw new Error(`Missing precache asset: ${url} (${res.status})`);\n}","typeGuard":null,"tryCatchPattern":"// Per-entry catch that logs the entry, then rethrows so install still fails loudly\nPRECACHE_MANIFEST.map(async (entry) => {\n\ttry { /* fetch + cache.put */ } catch (e) {\n\t\tconsole.error(`Precache failed for ${entry.url}`, e);\n\t\tthrow e;\n\t}\n});","preventionTips":["Generate the precache manifest in the same build step that emits the hashed assets","Upload dist/ atomically (all files or nothing) so the worker never sees a half-deployed manifest","Add a CI Playwright/Puppeteer step that registers the service worker and waits for a clean install","Use { cache: \"reload\" } on install-time fetches to bypass stale HTTP cache entries"],"tags":["service-worker","precache","install","deployment","vite"],"backgroundTag":"service-worker-precache-failure","analyzedSha":"c507b356453361e31638b8bff8f6d46b6da2961e","analyzedAt":"2026-08-16T21:57:18.596Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}