{"record":{"id":"925b2196b92ee5ea","repo":"denoland/deno","slug":"deno-autoupdate-latest-json-is-not-valid-json","errorCode":null,"errorMessage":"Deno.autoUpdate: latest.json is not valid JSON","messagePattern":"Deno\\.autoUpdate: latest\\.json is not valid JSON","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cli/rt/desktop.rs","lineNumber":1110,"sourceCode":"      return;\n    }}\n\n    const base = url.replace(/\\/$/, \"\");\n    const te = new TextEncoder();\n\n    const check = async () => {{\n      try {{\n        const resp = await fetch(base + \"/latest.json\", {{\n          cache: \"no-store\",\n          redirect: \"error\",\n        }});\n        if (!resp.ok) return;\n        const manifestText = await resp.text();\n        let manifest;\n        try {{\n          manifest = JSON.parse(manifestText);\n        }} catch {{\n          console.warn(\"Deno.autoUpdate: latest.json is not valid JSON\");\n          return;\n        }}\n        if (manifest.version === _version) return;\n\n        if (publicKey) {{\n          const sig = manifest.signature;\n          if (typeof sig !== \"string\" || !sig) {{\n            console.error(\n              \"Deno.autoUpdate: publicKey configured but manifest has no signature\",\n            );\n            return;\n          }}\n          // Signature is computed over the manifest with the `signature` field\n          // removed, serialized canonically. To avoid depending on a JCS\n          // implementation, signers must put the signature on a top-level\n          // `signature` field and include the rest of the manifest verbatim\n          // under a `signed` field (string). We then verify over that string.\n          const signed = manifest.signed;","sourceCodeStart":1092,"sourceCodeEnd":1128,"githubUrl":"https://github.com/denoland/deno/blob/f7822238cab635a3a19f99f493f675fa81a7f9d8/cli/rt/desktop.rs#L1092-L1128","documentation":"During a periodic auto-update check, Deno fetched <base>/latest.json and got an HTTP 200 response, but the body failed JSON.parse (cli/rt/desktop.rs:1037-1041). The runtime treats the manifest as unusable, warns, and skips this check round. The app stays on its current version; the next interval tick will retry.","triggerScenarios":"The update server returns 200 with non-JSON content: an HTML error/SPA fallback page, a truncated body, a BOM-prefixed file, a JSONC file with comments/trailing commas, or proxy/CDN-injected markup. Note resp.ok was already true, so this is purely a body-parsing failure, and redirect: 'error' means a redirect would have failed earlier, not here.","commonSituations":"Static hosts configured to serve index.html for unknown paths; hand-edited latest.json with a trailing comma; CDN transformations applied to the update path; publishing latest.json as JSONC from the release script.","solutions":["curl -f <base>/latest.json from a clean network and run the body through a JSON validator to see the exact parse error","Fix the server so /latest.json serves real JSON with Content-Type: application/json (disable HTML fallback for that path)","Republish latest.json as strict JSON: no comments, no trailing commas, no BOM","Check any proxy/CDN in front of the update host for body rewriting or truncation"],"exampleFix":"# before: updates.example.com serves the SPA fallback for /myapp/latest.json\n$ curl -s https://updates.example.com/myapp/latest.json\n<html><body>404</body></html>   # 200 + HTML -> 'not valid JSON' warning\n\n# after: strict JSON manifest\n{\"version\":\"1.2.0\",\"patches\":{\"1.1.0\":{\"name\":\"1.1.0-to-1.2.0.patch\",\"sha256\":\"ab...\"}}}","handlingStrategy":"validation","validationCode":"// CI: validate exactly what the app will fetch before you publish\nconst res = await fetch(`${BASE}/latest.json`);\nif (!res.ok) throw new Error(`latest.json unreachable: ${res.status}`);\nconst manifest = JSON.parse(await res.text()); // throws if the host serves anything but strict JSON\nif (typeof manifest?.version !== \"string\" || typeof manifest?.patches !== \"object\") {\n  throw new Error(\"latest.json: missing version/patches\");\n}","typeGuard":"function isManifest(v: unknown): v is { version: string; patches: Record<string, { name: string; sha256: string }> } {\n  if (typeof v !== \"object\" || v === null) return false;\n  const m = v as { version?: unknown; patches?: unknown };\n  return typeof m.version === \"string\" && typeof m.patches === \"object\" && m.patches !== null;\n}","tryCatchPattern":null,"preventionTips":["Serve latest.json with Content-Type: application/json and no HTML/SPA fallback on that path","Lint the manifest in CI with strict JSON.parse before publishing","Disable CDN rewriting or minification on the update path","Keep the interval option set so a transient bad body is retried on the next tick"],"tags":["desktop","auto-update","json","manifest","update-server"],"backgroundTag":"invalid-json-response","analyzedSha":"f7822238cab635a3a19f99f493f675fa81a7f9d8","analyzedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-08-31T04:17:50.494Z"}