{"record":{"id":"6d47c8f49b3ed9ea","repo":"mihomo-party-org/clash-party","slug":"invalid-latest-yml-from-update-source","errorCode":null,"errorMessage":"Invalid latest.yml from update source","messagePattern":"Invalid latest\\.yml from update source","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/resolve/autoUpdater.ts","lineNumber":112,"sourceCode":"    throw new Error(`GitHub Release does not provide a SHA-256 digest for \"${file}\"`)\n  }\n  return match[1].toLowerCase()\n}\n\nexport async function checkUpdate(): Promise<IAppVersion | undefined> {\n  const [{ 'mixed-port': mixedPort = DEFAULT_MIHOMO_PORTS.mixed }, { githubProxy = '' }] =\n    await Promise.all([getControledMihomoConfig(), getAppConfig()])\n  const githubUrl =\n    'https://github.com/mihomo-party-org/mihomo-party/releases/latest/download/latest.yml'\n  const res = await tryDownload(buildDownloadUrls(githubUrl, githubProxy), {\n    headers: { 'Content-Type': 'application/octet-stream' },\n    proxy: updaterProxy(mixedPort),\n    responseType: 'text'\n  })\n  const latest = parse(res.data as string) as IAppVersion\n  // 错误页也能被 YAML 解析成对象（如 `404: Not Found`），不校验会让 compareVersions 崩在 undefined.replace\n  if (!latest || typeof latest.version !== 'string') {\n    throw new Error('Invalid latest.yml from update source')\n  }\n  const currentVersion = app.getVersion()\n  if (compareVersions(latest.version, currentVersion) > 0) {\n    return latest\n  } else {\n    return undefined\n  }\n}\n\n// 1:新 -1:旧 0:相同\nfunction compareVersions(a: string, b: string): number {\n  const parsePart = (part: string) => {\n    const numPart = part.split('-')[0]\n    const num = parseInt(numPart, 10)\n    return isNaN(num) ? 0 : num\n  }\n  const v1 = a.replace(/^v/, '').split('.').map(parsePart)\n  const v2 = b.replace(/^v/, '').split('.').map(parsePart)","sourceCodeStart":94,"sourceCodeEnd":130,"githubUrl":"https://github.com/mihomo-party-org/clash-party/blob/911e090537acdf7c50bee1c3aebecc2ef119a8b5/src/main/resolve/autoUpdater.ts#L94-L130","documentation":"checkUpdate (src/main/resolve/autoUpdater.ts:112) downloads latest.yml as text and parses it with YAML parse into IAppVersion. Error pages from proxies can still parse as a YAML object (e.g. `404: Not Found`), so the code validates that the result exists and latest.version is a string before compareVersions (which would crash on undefined.replace). Anything failing this shape check throws 'Invalid latest.yml from update source'.","triggerScenarios":"The latest.yml endpoint returns an error page (404/403 HTML or text like `404: Not Found`) that YAML-parses into an object without a string `version` field; the response is empty, or valid YAML whose top level has no version key.","commonSituations":"A proxy mirror (githubProxy) serves its own error/rate-limit page instead of latest.yml; the release was deleted so latest.yml 404s; a captive portal or firewall injects an HTML block page; the update source URL is misconfigured to a non-GitHub host.","solutions":["Check/clear the githubProxy setting so latest.yml is fetched from a reachable, correct source","Open the latest.yml URL in a browser and confirm it returns YAML containing `version: x.y.z`","If 404, verify the release/tag still exists on the update source","Retry later if the proxy is rate-limiting or temporarily serving error pages","Ensure compareVersions-like consumers always run after this validation (already enforced by the throw)"],"exampleFix":"// before\nconst latest = parse(res.data as string) as IAppVersion\ncompareVersions(latest.version, currentVersion) // crashes on '404: Not Found' page\n// after\nconst latest = parse(res.data as string) as IAppVersion\nif (!latest || typeof latest.version !== 'string') {\n  throw new Error('Invalid latest.yml from update source')\n}","handlingStrategy":"validation","validationCode":"const text = res.data as string\nconst latest = parse(text) as IAppVersion\nif (!latest || typeof latest.version !== 'string') {\n  throw new Error('Invalid latest.yml from update source')\n}","typeGuard":"function isValidAppVersion(v: unknown): v is IAppVersion {\n  return !!v && typeof v === 'object' && typeof (v as IAppVersion).version === 'string'\n}","tryCatchPattern":"try {\n  const latest = await checkUpdate()\n  if (latest) promptUpdate(latest)\n} catch (e) {\n  if (e instanceof Error && e.message === 'Invalid latest.yml from update source') {\n    // proxy served an error page; skip this cycle or retry with another source\n    console.warn('update check skipped:', e.message)\n  }\n}","preventionTips":["Validate parsed YAML shape (version is a string) before any string operations like compareVersions","Check what the latest.yml URL actually returns through configured proxies; error pages parse as YAML too","Clear/replace a githubProxy that serves HTML or rate-limit pages instead of release files","Run update checks with graceful failure — a bad source should skip the cycle, not crash the app"],"tags":["updater","yaml","validation","network","proxy"],"backgroundTag":"invalid-update-metadata","analyzedSha":"911e090537acdf7c50bee1c3aebecc2ef119a8b5","analyzedAt":"2026-08-30T13:00:49.174Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}