{"record":{"id":"28d97cf20f856b3f","repo":"vercel/hyper","slug":"malformed-server-response-release-name-is-missing","errorCode":null,"errorMessage":"Malformed server response: release name is missing.","messagePattern":"Malformed server response: release name is missing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"app/auto-updater-linux.ts","lineNumber":33,"sourceCode":"    this.updateURL = options.url;\n  }\n\n  checkForUpdates() {\n    if (!this.updateURL) {\n      return this.emitError('Update URL is not set');\n    }\n    this.emit('checking-for-update');\n\n    fetch(this.updateURL)\n      .then((res) => {\n        if (res.status === 204) {\n          this.emit('update-not-available');\n          return;\n        }\n        return res.json().then(({name, notes, pub_date}: {name: string; notes: string; pub_date: string}) => {\n          // Only name is mandatory, needed to construct release URL.\n          if (!name) {\n            throw new Error('Malformed server response: release name is missing.');\n          }\n          const date = pub_date ? new Date(pub_date) : new Date();\n          this.emit('update-available', {}, notes, name, date);\n        });\n      })\n      .catch(this.emitError.bind(this));\n  }\n\n  emitError(error: string | Error) {\n    if (typeof error === 'string') {\n      error = new Error(error);\n    }\n    this.emit('error', error);\n  }\n}\n\nconst autoUpdaterLinux = new AutoUpdater();\n","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/vercel/hyper/blob/da0c401d7f9e197b1fa6f29854adffa43d3a3287/app/auto-updater-linux.ts#L15-L51","documentation":"Thrown by Hyper's Linux AutoUpdater (app/auto-updater-linux.ts) after it fetches the configured update feed and parses the response as JSON. The feed contract requires an object with {name, notes, pub_date}, and `name` is the only mandatory field because it is used to construct the release download URL. If the parsed body has no `name` (or `name` is falsy/undefined), the updater cannot proceed and throws. The throw happens inside a resolved promise and is funneled to `emitError`, so it surfaces as an `'error'` event on the autoUpdater EventEmitter rather than a synchronous exception.","triggerScenarios":"Calling `autoUpdaterLinux.checkForUpdates()` after `setFeedURL({url})` where `url` resolves to a 200/2xx response whose JSON body lacks a `name` key (e.g. `{}`, `{notes, pub_date}` without name, a GitHub API rate-limit JSON, a redirect/captive-portal JSON page, or any non-update JSON the server happens to return). A 204 short-circuits to `update-not-available` before this check, so the response must be non-204 with a JSON content-type that parses.","commonSituations":"Update URL misconfigured to point at a wrong endpoint (HTML landing page whose JSON parse yields empty object, GitHub API release JSON with `tag_name`/`name` mismatch, electron-builder's `latest.yml` served raw as YAML-in-JSON, proxy replacing the body with a block page that still deserializes, CDN serving a stale/malformed manifest after a botched release publish, or a v1/v2 feed schema change that renamed `name` to `version`).","solutions":["curl -i the exact updateURL from setFeedURL and confirm the JSON body contains a non-empty `name` field; the contract is {name: string, notes?: string, pub_date?: string}.","If you control the update server, ensure every non-204 release response includes `name` (typically the release tag/version string).","If using electron-builder, point the feed URL at the JSON it generates (or use electron-updater) instead of a hand-rolled endpoint that omits `name`.","Register an `autoUpdater.on('error', err => ...)` listener so a malformed feed degrades gracefully instead of surfacing as an unhandled EventEmitter error (Node throws on unhandled 'error' events).","Verify no proxy/CDN is rewriting the response body; check the `content-type` and raw bytes, not just the status code."],"exampleFix":"// before\nautoUpdaterLinux.setFeedURL({url: 'https://example.com/releases/latest'});\nautoUpdaterLinux.checkForUpdates();\n// server returns {} or {notes, pub_date} with no name -> 'Malformed server response: release name is missing.'\n\n// after — fix the feed to include name AND guard the consumer\n// server-side: respond with {\"name\":\"v3.1.0\",\"notes\":\"...\",\"pub_date\":\"2026-08-12T00:00:00Z\"}\nimport autoUpdaterLinux from './app/auto-updater-linux';\nautoUpdaterLinux.setFeedURL({url: 'https://example.com/releases/latest'});\nautoUpdaterLinux.on('error', (err) => {\n  // graceful degradation — never let an EventEmitter 'error' go unhandled\n  console.warn('Update check failed:', err.message);\n});\nautoUpdaterLinux.checkForUpdates();","handlingStrategy":"try-catch","validationCode":"// Pre-flight the feed before handing the URL to the AutoUpdater so a malformed\n// server never reaches the throw site. Run once at app start or on URL change.\nimport fetch from 'electron-fetch';\n\nasync function assertFeedShape(url: string): Promise<void> {\n  const res = await fetch(url);\n  if (res.status === 204) return; // update-not-available, no body needed\n  if (!res.ok) throw new Error(`Feed HTTP ${res.status}`);\n  const body = await res.json() as unknown;\n  if (\n    typeof body !== 'object' ||\n    body === null ||\n    typeof (body as { name?: unknown }).name !== 'string' ||\n    (body as { name: string }).name.length === 0\n  ) {\n    throw new Error(`Feed at ${url} is missing a non-empty \\`name\\` field`);\n  }\n}\n\n// usage\nawait assertFeedShape(autoUpdaterLinux.getFeedURL());\nautoUpdaterLinux.checkForUpdates();","typeGuard":"// Narrow the parsed feed before touching required fields.\nimport type { ReleaseFeed } from './release-feed'; // { name: string; notes?: string; pub_date?: string }\n\nfunction isReleaseFeed(v: unknown): v is ReleaseFeed {\n  if (typeof v !== 'object' || v === null) return false;\n  const name = (v as { name?: unknown }).name;\n  if (typeof name !== 'string' || name.length === 0) return false;\n  return true;\n}\n\nconst body: unknown = await res.json();\nif (!isReleaseFeed(body)) {\n  throw new Error('Malformed server response: release name is missing.');\n}\n// body.name is now string","tryCatchPattern":"// AutoUpdater is an EventEmitter; the throw is converted to an 'error' event.\n// Always attach a listener BEFORE checkForUpdates — Node throws on unhandled 'error'.\nimport autoUpdaterLinux from './app/auto-updater-linux';\n\nautoUpdaterLinux.once('error', (err: Error) => {\n  if (/release name is missing/i.test(err.message)) {\n    // Known shape: feed contract violation. Degrade silently, schedule retry.\n    console.warn('Update feed malformed, skipping this check:', err.message);\n    return;\n  }\n  // Unknown cause — surface to app-level error reporting.\n  throw err;\n});\nautoUpdaterLinux.on('update-available', (_e, notes, name, date) => {\n  /* ... */\n});\nautoUpdaterLinux.checkForUpdates();","preventionTips":["Pin setFeedURL to an endpoint you control that always emits {name, notes, pub_date} for non-204 responses.","Never point the feed URL at a raw GitHub web URL or HTML page; use the releases API JSON or electron-builder's generated feed.","Always register an 'error' listener on the AutoUpdater before calling checkForUpdates — an unhandled EventEmitter 'error' crashes the process.","Smoke-test the feed with curl in CI whenever you change release tooling, asserting jq -e '.name | type == \"string\" and length > 0'.","Treat 204 as the only 'no update' signal; do not return 200 with an empty body."],"tags":["network","auto-update","electron","linux","eventemitter","json"],"backgroundTag":null,"analyzedSha":"da0c401d7f9e197b1fa6f29854adffa43d3a3287","analyzedAt":"2026-08-12T19:28:14.584Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}