{"record":{"id":"85cdb6946debb932","repo":"jamiepine/voicebox","slug":"failed-to-fetch-releases","errorCode":null,"errorMessage":"Failed to fetch releases","messagePattern":"Failed to fetch releases","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"landing/src/app/capture/page.tsx","lineNumber":20,"sourceCode":"\nimport { Github } from 'lucide-react';\nimport { useEffect, useState } from 'react';\nimport { AgentIntegration } from '@/components/AgentIntegration';\nimport { CaptureHero } from '@/components/CaptureHero';\nimport { CapturesMockup } from '@/components/CapturesMockup';\nimport { Footer } from '@/components/Footer';\nimport { Navbar } from '@/components/Navbar';\nimport { AppleIcon, LinuxIcon, WindowsIcon } from '@/components/PlatformIcons';\nimport { GITHUB_REPO } from '@/lib/constants';\n\nexport default function CapturePage() {\n  const [version, setVersion] = useState<string | null>(null);\n  const [totalDownloads, setTotalDownloads] = useState<number | null>(null);\n\n  useEffect(() => {\n    fetch('/api/releases')\n      .then((res) => {\n        if (!res.ok) throw new Error('Failed to fetch releases');\n        return res.json();\n      })\n      .then((data) => {\n        if (data.version) setVersion(data.version);\n        if (data.totalDownloads != null) setTotalDownloads(data.totalDownloads);\n      })\n      .catch((error) => {\n        console.error('Failed to fetch release info:', error);\n      });\n  }, []);\n\n  return (\n    <>\n      <Navbar />\n\n      {/* ── Hero ─────────────────────────────────────────────────── */}\n      <CaptureHero version={version} totalDownloads={totalDownloads} />\n","sourceCodeStart":2,"sourceCodeEnd":38,"githubUrl":"https://github.com/jamiepine/voicebox/blob/51f49dea198384b4eb6087b72c17057c6eb1c1cd/landing/src/app/capture/page.tsx#L2-L38","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the server logs for getLatestRelease()'s underlying GitHub status (the /api/releases route logs 'Error fetching release info').","If rate-limited, configure a GitHub token for the server-side fetch to raise the limit.","Treat null version gracefully in the UI (hide the version badge instead of showing an error).","Confirm GITHUB_REPO in landing/src/lib/releases.ts is correct and public."],"exampleFix":"// before\nif (!res.ok) throw new Error('Failed to fetch releases');\n// after\nif (!res.ok) { console.warn('releases status', res.status); return; }","handlingStrategy":"fallback","validationCode":null,"typeGuard":"interface ReleasePayload { version?: string; totalDownloads?: number; error?: string }\nfunction isReleasePayload(v: unknown): v is ReleasePayload {\n  return typeof v === 'object' && v !== null && (!('error' in v));\n}","tryCatchPattern":"try {\n  const res = await fetch('/api/releases');\n  if (!res.ok) throw new Error('Failed to fetch releases');\n  const data = await res.json();\n  if (data.version) setVersion(data.version);\n} catch (error) {\n  console.error('Failed to fetch release info:', error);\n  // version stays null; UI hides the badge\n}","preventionTips":["Treat null version as 'hide the badge' rather than an error state.","Configure a GitHub token server-side to avoid the 60/hr unauthenticated cap.","Confirm GITHUB_REPO is correct and public."],"tags":["network","external-api","github","nextjs","releases","landing"],"backgroundTag":null,"analyzedSha":"51f49dea198384b4eb6087b72c17057c6eb1c1cd","analyzedAt":"2026-08-12T16:51:42.824Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}