jamiepine/voicebox · warning · Error

GitHub API error: ${response.status}

Error message

GitHub API error: ${response.status}

What it means

Thrown by `getLatestRelease()` in `landing/src/lib/releases.ts` (line 45-47) when GitHub's `GET /repos/jamiepine/voicebox/releases/latest` returns non-2xx. The call is unauthenticated (`cache: 'no-store'`, no Authorization header), so it is subject to GitHub's 60-requests-per-hour-per-IP unauthenticated rate limit. There is a 5-minute in-memory cache that absorbs repeat load, but a cold cache during heavy traffic still hits the limit.

Source

Thrown at landing/src/lib/releases.ts:46

 * Fetches the latest release from GitHub and extracts download links
 */
export async function getLatestRelease(): Promise<ReleaseInfo> {
  // Return cached data if still valid
  const now = Date.now();
  if (cachedReleaseInfo && now - cacheTimestamp < CACHE_DURATION) {
    return cachedReleaseInfo;
  }

  try {
    const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/latest`, {
      cache: 'no-store',
      headers: {
        Accept: 'application/vnd.github.v3+json',
      },
    });

    if (!response.ok) {
      throw new Error(`GitHub API error: ${response.status}`);
    }

    const release = await response.json();
    const version = release.tag_name;
    const assets = release.assets || [];

    // Extract download links based on file patterns
    const downloadLinks: Partial<DownloadLinks> = {};

    for (const asset of assets) {
      const name = asset.name.toLowerCase();
      const url = asset.browser_download_url;

      // Skip signature files and other non-downloadable files
      if (name.endsWith('.sig') || name.endsWith('.json') || name.endsWith('.txt')) {
        continue;
      }

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Add a GitHub Personal Access Token (fine-grained, public-read) as an `Authorization: Bearer <token>` header to lift the limit to 5000/hour; store it server-side as an env var.
  2. Honor `X-RateLimit-Reset` on a 403 and serve the last cached `cachedReleaseInfo` (extend the cache to survive rate-limit windows).
  3. If the repo has no latest release, fall back to listing `/releases` and pick the first, or surface 'no release available'.
  4. Keep `GITHUB_REPO` in sync with the actual repo slug after any rename.

Example fix

// before
const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/latest`, {
  cache: 'no-store',
  headers: { Accept: 'application/vnd.github.v3+json' },
});

// after — authenticated + rate-limit aware
const headers: Record<string,string> = { Accept: 'application/vnd.github.v3+json' };
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
const response = await fetch(`${GITHUB_API_BASE}/repos/${GITHUB_REPO}/releases/latest`, {
  next: { revalidate: 300 },
  headers,
});
if (response.status === 403 && cachedReleaseInfo) return cachedReleaseInfo;
Defensive patterns

Strategy: fallback

Validate before calling

// Authenticate when a token is available, and reuse the cache
const headers: Record<string,string> = { Accept: 'application/vnd.github.v3+json' };
if (process.env.GITHUB_TOKEN) headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;

Type guard

function isRateLimited(res: Response): boolean {
  return res.status === 403
    && res.headers.get('X-RateLimit-Remaining') === '0';
}

Try / catch

try {
  return await getLatestRelease();
} catch (e) {
  if (cachedReleaseInfo) return cachedReleaseInfo; // stale cache beats nothing
  return null; // render a 'release info unavailable' state
}

Prevention

When it happens

Trigger: Unauthenticated rate limit exceeded (HTTP 403 with `X-RateLimit-Remaining: 0`); the repo was renamed/moved so the path 404s; the repo has no published releases so `releases/latest` returns 404; GitHub API outage (5xx); network/DNS failure reaching api.github.com.

Common situations: A traffic spike to the landing page exhausts the 60 req/hour limit from a shared egress IP (CDN/NAT); repo was renamed and the constant `GITHUB_REPO` is stale; project has never published a GitHub Release (only tags); shared hosting NAT means many sites share one IP's quota.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/7db5f96f10d69fbb. Report an issue: GitHub.