iOfficeAI/AionUi · error · Error

update.errors.githubApiNotArray

update.errors.githubApiNotArray

Error message

update.errors.githubApiNotArray

What it means

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().

Source

Thrown at packages/desktop/src/process/bridge/updateBridge.ts:337

  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const res = await fetch(url, {
      headers: {
        Accept: 'application/vnd.github+json',
        'User-Agent': DEFAULT_USER_AGENT,
      },
      signal: controller.signal,
    });

    if (!res.ok) {
      throw new Error((await getI18n()).t('update.errors.githubApiFailed', { status: res.status }));
    }

    const json = (await res.json()) as unknown;
    if (!Array.isArray(json)) {
      throw new Error((await getI18n()).t('update.errors.githubApiNotArray'));
    }
    return json as GitHubReleaseApi[];
  } catch (err: unknown) {
    if (err instanceof Error && err.name === 'AbortError') {
      throw new Error((await getI18n()).t('update.errors.githubApiTimeout'), { cause: err });
    }
    throw err;
  } finally {
    clearTimeout(timeoutId);
  }
};

const CDN_MANIFEST_TIMEOUT_MS = 15000;
const GITHUB_NOTES_TIMEOUT_MS = 10000;

/**
 * Fetch and parse the authoritative CDN channel manifest for the current
 * platform/arch. Any failure here fails the manual check — the CDN is the

View on GitHub (pinned to 711aa0550e)

Solutions

  1. Log/inspect the actual response body to see what came back instead of an array
  2. If a proxy is rewriting responses, bypass it or allowlist api.github.com
  3. Retry — transient proxy interference often clears
  4. If the endpoint shape changed upstream, update fetchGitHubReleases parsing to match the documented API

Example fix

// before
const json = (await res.json()) as unknown;
if (!Array.isArray(json)) throw ...;

// after
const json = (await res.json()) as unknown;
if (!Array.isArray(json)) {
  log.warn('[update] unexpected GitHub API body:', JSON.stringify(json).slice(0, 200));
  throw ...;
}
Defensive patterns

Strategy: type-guard

Type guard

const isReleaseArray = (v: unknown): v is GitHubReleaseApi[] =>
  Array.isArray(v) && v.every((r) => typeof r === 'object' && r !== null && 'tag_name' in r);

Try / catch

try {
  const releases = await fetchGitHubReleases(repo);
} catch (err) {
  if (err instanceof Error && err.message.includes('githubApiNotArray')) {
    // likely proxy interference; log body context and fall back to CDN manifest
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of iOfficeAI/AionUi@711aa0550e (2026-08-28). Data as JSON: /api/errors/49b07064cfe9dfb0. Report an issue: GitHub.