{"record":{"id":"49b07064cfe9dfb0","repo":"iOfficeAI/AionUi","slug":"update-errors-githubapinotarray","errorCode":"update.errors.githubApiNotArray","errorMessage":"update.errors.githubApiNotArray","messagePattern":"update\\.errors\\.githubApiNotArray","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/desktop/src/process/bridge/updateBridge.ts","lineNumber":337,"sourceCode":"  const controller = new AbortController();\n  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n  try {\n    const res = await fetch(url, {\n      headers: {\n        Accept: 'application/vnd.github+json',\n        'User-Agent': DEFAULT_USER_AGENT,\n      },\n      signal: controller.signal,\n    });\n\n    if (!res.ok) {\n      throw new Error((await getI18n()).t('update.errors.githubApiFailed', { status: res.status }));\n    }\n\n    const json = (await res.json()) as unknown;\n    if (!Array.isArray(json)) {\n      throw new Error((await getI18n()).t('update.errors.githubApiNotArray'));\n    }\n    return json as GitHubReleaseApi[];\n  } catch (err: unknown) {\n    if (err instanceof Error && err.name === 'AbortError') {\n      throw new Error((await getI18n()).t('update.errors.githubApiTimeout'), { cause: err });\n    }\n    throw err;\n  } finally {\n    clearTimeout(timeoutId);\n  }\n};\n\nconst CDN_MANIFEST_TIMEOUT_MS = 15000;\nconst GITHUB_NOTES_TIMEOUT_MS = 10000;\n\n/**\n * Fetch and parse the authoritative CDN channel manifest for the current\n * platform/arch. Any failure here fails the manual check — the CDN is the","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/iOfficeAI/AionUi/blob/711aa0550ee183ea495dc33e2c05c7943b70a60a/packages/desktop/src/process/bridge/updateBridge.ts#L319-L355","documentation":"Thrown by fetchGitHubReleases when the GitHub Releases API responds 200 OK but the JSON body is not an array. The GitHub releases endpoint is expected to return a JSON array of release objects; any other top-level shape (usually an error object) fails this type check after res.json().","triggerScenarios":"A 200 response whose body is an object — commonly a proxy/interceptor returning {error: ...}, a captive portal returning an HTML/JSON hybrid with 200, or a GitHub API change in response shape. Also possible if the URL was rewritten (e.g. enterprise proxy) to a different JSON endpoint.","commonSituations":"Corporate proxies returning 200 with a JSON error body; DNS hijacking/captive portals; API version drift on GitHub's side; a repo redirect that returns an object message instead of the releases array.","solutions":["Log/inspect the actual response body to see what came back instead of an array","If a proxy is rewriting responses, bypass it or allowlist api.github.com","Retry — transient proxy interference often clears","If the endpoint shape changed upstream, update fetchGitHubReleases parsing to match the documented API"],"exampleFix":"// before\nconst json = (await res.json()) as unknown;\nif (!Array.isArray(json)) throw ...;\n\n// after\nconst json = (await res.json()) as unknown;\nif (!Array.isArray(json)) {\n  log.warn('[update] unexpected GitHub API body:', JSON.stringify(json).slice(0, 200));\n  throw ...;\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"const isReleaseArray = (v: unknown): v is GitHubReleaseApi[] =>\n  Array.isArray(v) && v.every((r) => typeof r === 'object' && r !== null && 'tag_name' in r);","tryCatchPattern":"try {\n  const releases = await fetchGitHubReleases(repo);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('githubApiNotArray')) {\n    // likely proxy interference; log body context and fall back to CDN manifest\n  } else throw err;\n}","preventionTips":["Log the raw body when shape validation fails","Bypass corporate proxies for api.github.com or pin them via allowlist","Pin tests to GitHub's documented schema to catch API drift"],"tags":["github-api","json-validation","updater"],"backgroundTag":"unexpected-response-shape","analyzedSha":"711aa0550ee183ea495dc33e2c05c7943b70a60a","analyzedAt":"2026-08-28T07:56:06.558Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}