jamiepine/voicebox · info · Error

Failed to fetch releases

Error message

Failed to fetch releases

What it means

Thrown inside a useEffect on the /capture page when the internal /api/releases route returns non-2xx. The promise chain catches it and only console.errors, so the page still renders — version/totalDownloads stay null. /api/releases proxies GitHub's releases API via getLatestRelease() and returns 500 if that upstream fetch fails.

Source

Thrown at landing/src/app/capture/page.tsx:20

import { Github } from 'lucide-react';
import { useEffect, useState } from 'react';
import { AgentIntegration } from '@/components/AgentIntegration';
import { CaptureHero } from '@/components/CaptureHero';
import { CapturesMockup } from '@/components/CapturesMockup';
import { Footer } from '@/components/Footer';
import { Navbar } from '@/components/Navbar';
import { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';
import { GITHUB_REPO } from '@/lib/constants';

export default function CapturePage() {
  const [version, setVersion] = useState<string | null>(null);
  const [totalDownloads, setTotalDownloads] = useState<number | null>(null);

  useEffect(() => {
    fetch('/api/releases')
      .then((res) => {
        if (!res.ok) throw new Error('Failed to fetch releases');
        return res.json();
      })
      .then((data) => {
        if (data.version) setVersion(data.version);
        if (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);
      })
      .catch((error) => {
        console.error('Failed to fetch release info:', error);
      });
  }, []);

  return (
    <>
      <Navbar />

      {/* ── Hero ─────────────────────────────────────────────────── */}
      <CaptureHero version={version} totalDownloads={totalDownloads} />

View on GitHub (pinned to 51f49dea19)

Solutions

  1. Check the server logs for getLatestRelease()'s underlying GitHub status (the /api/releases route logs 'Error fetching release info').
  2. If rate-limited, configure a GitHub token for the server-side fetch to raise the limit.
  3. Treat null version gracefully in the UI (hide the version badge instead of showing an error).
  4. Confirm GITHUB_REPO in landing/src/lib/releases.ts is correct and public.

Example fix

// before
if (!res.ok) throw new Error('Failed to fetch releases');
// after
if (!res.ok) { console.warn('releases status', res.status); return; }
Defensive patterns

Strategy: fallback

Type guard

interface ReleasePayload { version?: string; totalDownloads?: number; error?: string }
function isReleasePayload(v: unknown): v is ReleasePayload {
  return typeof v === 'object' && v !== null && (!('error' in v));
}

Try / catch

try {
  const res = await fetch('/api/releases');
  if (!res.ok) throw new Error('Failed to fetch releases');
  const data = await res.json();
  if (data.version) setVersion(data.version);
} catch (error) {
  console.error('Failed to fetch release info:', error);
  // version stays null; UI hides the badge
}

Prevention

When it happens

Trigger: GET /api/releases responds 500 because getLatestRelease() threw (GitHub API rate-limited at 403/429, repo not found, network). The deployed Next.js instance's egress to api.github.com is blocked.

Common situations: GitHub unauthenticated rate limit (60 req/hr per IP) exhausted by a shared deploy or preview builds. GITHUB_REPO constant points at a non-existent or private repo. CI/preview environment with no outbound network.

Related errors


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