schollz/croc · error

GitHub returned invalid release metadata

Error message

GitHub returned invalid release metadata

What it means

Thrown by fetchLatestRelease() when GitHub returns 200 but the JSON body is not release-shaped: tag_name or html_url missing/falsy, or assets not an array. It protects the downstream asset-selection code (assetArchitecture / assets.find) from an unexpected payload shape.

Source

Thrown at web/src/releases.ts:138

    assets[0]
  );
}

export async function fetchLatestRelease(signal?: AbortSignal) {
  const response = await fetch(latestReleaseAPI, {
    signal,
    headers: { Accept: "application/vnd.github+json" },
  });
  if (!response.ok) {
    throw new Error(`GitHub release request failed (${response.status})`);
  }
  const release = (await response.json()) as GitHubRelease;
  if (
    !release.tag_name ||
    !release.html_url ||
    !Array.isArray(release.assets)
  ) {
    throw new Error("GitHub returned invalid release metadata");
  }
  return release;
}

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. In tests, mock fetch with { tag_name, html_url, assets: [...] }
  2. Bypass or fix intermediaries that rewrite api.github.com responses
  3. Track the GitHub REST API changelog for release schema changes

Example fix

// before (test stub that triggers the error)
fetchMock.mockResolvedValue(new Response(JSON.stringify({}), { status: 200 }));

// after
fetchMock.mockResolvedValue(new Response(JSON.stringify({
  tag_name: 'v1.0.0',
  html_url: 'https://github.com/o/r/releases/v1.0.0',
  assets: [],
}), { status: 200 }));
Defensive patterns

Strategy: type-guard

Validate before calling

const body = await response.json();
if (!isGitHubRelease(body)) throw new Error('unexpected release payload shape');

Type guard

function isGitHubRelease(v: unknown): v is GitHubRelease {
  const r = v as Record<string, unknown>;
  return typeof r?.tag_name === 'string' && r.tag_name !== '' &&
    typeof r?.html_url === 'string' && Array.isArray(r?.assets);
}

Try / catch

try { return await fetchLatestRelease(signal); } catch (e) { if (e instanceof Error && e.message === 'GitHub returned invalid release metadata') invalidateReleaseCache(); throw e; }

Prevention

When it happens

Trigger: A 200 response lacking tag_name/html_url/assets: a proxy returning an error page with 200, a test fetch mock returning {}, or a GitHub API schema change after deprecation.

Common situations: Test fixtures stubbing fetch with minimal objects; intercepting proxies rewriting responses; future GitHub API versions dropping fields.

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/f1b420d249380f21. Report an issue: GitHub.