{"record":{"id":"864ac16f64b44a53","repo":"iOfficeAI/AionUi","slug":"update-errors-redirectnolocation","errorCode":"update.errors.redirectNoLocation","errorMessage":"update.errors.redirectNoLocation","messagePattern":"update\\.errors\\.redirectNoLocation","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/desktop/src/process/bridge/updateBridge.ts","lineNumber":303,"sourceCode":"\nconst fetchWithAllowlistedRedirects = async (rawUrl: string, signal: AbortSignal): Promise<Response> => {\n  let current = rawUrl;\n\n  for (let i = 0; i <= MAX_REDIRECTS; i++) {\n    await assertAllowedUrl(current);\n\n    const res = await fetch(current, {\n      signal,\n      redirect: 'manual',\n      headers: {\n        'User-Agent': DEFAULT_USER_AGENT,\n      },\n    });\n\n    if (res.status >= 300 && res.status < 400) {\n      const location = res.headers.get('location');\n      if (!location) {\n        throw new Error((await getI18n()).t('update.errors.redirectNoLocation'));\n      }\n      current = new URL(location, current).toString();\n      continue;\n    }\n\n    return res;\n  }\n\n  throw new Error((await getI18n()).t('update.errors.tooManyRedirects'));\n};\n\nconst fetchGitHubReleases = async (repo: string, timeoutMs = 30000): Promise<GitHubReleaseApi[]> => {\n  const url = `https://api.github.com/repos/${repo}/releases`;\n\n  // 添加超时控制，防止网络问题导致无限等待 / Add timeout to prevent infinite wait on network issues\n  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n","sourceCodeStart":285,"sourceCodeEnd":321,"githubUrl":"https://github.com/iOfficeAI/AionUi/blob/711aa0550ee183ea495dc33e2c05c7943b70a60a/packages/desktop/src/process/bridge/updateBridge.ts#L285-L321","documentation":"Thrown by fetchWithAllowlistedRedirects when the server responds with a 3xx redirect status but the response has no Location header, making it impossible to follow the redirect chain. The manual redirect-following loop requires a location to compute the next URL (resolved relative to the current URL).","triggerScenarios":"An update feed or asset endpoint returning 301/302/307/308 without a Location header — misconfigured server, a stripped header from a proxy, or a status misuse where 3xx is returned as an error signal.","commonSituations":"CDN or nginx misconfiguration dropping Location; load balancer health checks responding 302 with no target; server returning 304 for conditional requests when the client used manual redirect mode; object storage presigned-URL flows that 3xx without Location when the signature expired.","solutions":["curl -I the failing URL and inspect the 3xx response for a Location header","Fix the server/proxy to emit Location on redirect responses","If the endpoint should not redirect, fix it to return the artifact or a 4xx directly","If a proxy strips Location, adjust its header forwarding config","Retry after correcting the upstream; this is a server-side issue"],"exampleFix":"// before\n// server responds: 302 with no Location header -> error\n\n// after (server side, nginx)\nreturn 302 https://github.com/owner/repo/releases/latest;","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const res = await fetchWithAllowlistedRedirects(url, signal);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('redirectNoLocation')) {\n    // retry once against the final known-good artifact URL, then surface error\n  } else throw err;\n}","preventionTips":["Verify feed URLs with curl -I before shipping config","Ensure proxies forward Location headers","Monitor updater failures server-side to catch header-stripping early"],"tags":["http","redirect","updater","network"],"backgroundTag":"redirect-without-location","analyzedSha":"711aa0550ee183ea495dc33e2c05c7943b70a60a","analyzedAt":"2026-08-28T07:56:06.558Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}