{"record":{"id":"33e0da01681c5501","repo":"janhq/jan","slug":"failed-to-fetch-releases","errorCode":null,"errorMessage":"Failed to fetch releases","messagePattern":"Failed to fetch releases","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/hooks/useReleaseNotes.ts","lineNumber":30,"sourceCode":"type ReleaseState = {\n  release: Release | null\n  loading: boolean\n  error: string | null\n  fetchLatestRelease: (includeBeta: boolean) => Promise<void>\n}\n\nexport const useReleaseNotes = create<ReleaseState>((set) => ({\n  release: null,\n  loading: false,\n  error: null,\n\n  fetchLatestRelease: async (includeBeta: boolean) => {\n    set({ loading: true, error: null })\n    try {\n      const res = await fetch(\n        'https://api.github.com/repos/janhq/jan/releases'\n      )\n      if (!res.ok) throw new Error('Failed to fetch releases')\n      const releases = await res.json()\n\n      const stableRelease = releases.find(\n        (release: { prerelease: boolean; draft: boolean }) =>\n          !release.prerelease && !release.draft\n      )\n      const betaRelease = releases.find(\n        (release: { prerelease: boolean }) => release.prerelease\n      )\n\n      const selected = includeBeta\n        ? (betaRelease ?? stableRelease)\n        : stableRelease\n      set({ release: selected, loading: false })\n    } catch (err: any) {\n      set({ error: err.message, loading: false })\n    }\n  },","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/hooks/useReleaseNotes.ts#L12-L48","documentation":"Thrown in useReleaseNotes.fetchLatestRelease when the GitHub API request to https://api.github.com/repos/janhq/jan/releases responds with a non-OK status. The hook needs the release list to compute the latest stable (non-prerelease, non-draft) release.","triggerScenarios":"fetch to the GitHub releases endpoint resolves but res.ok is false — 403 rate limiting, 404 if the repo moved, 5xx server errors, or blocked network/proxy responses.","commonSituations":"Unauthenticated GitHub API rate limit exceeded (60 req/hr per IP) on shared networks or CI; corporate proxy/VPN blocking api.github.com; GitHub incidents; repo renamed causing 404 with old hardcoded URL.","solutions":["Check the response status; if 403, wait for the GitHub API rate-limit window to reset (see X-RateLimit-Reset header)","Verify api.github.com is reachable (proxy/VPN/firewall) and retry","Add an Authorization token to the request to raise the rate limit","Check https://www.githubstatus.com for GitHub API incidents"],"exampleFix":"// before\nconst res = await fetch('https://api.github.com/repos/janhq/jan/releases')\nif (!res.ok) throw new Error('Failed to fetch releases')\n// after\nconst res = await fetch('https://api.github.com/repos/janhq/jan/releases')\nif (!res.ok) {\n  if (res.status === 403) throw new Error('GitHub API rate limit exceeded; try again later')\n  throw new Error(`Failed to fetch releases (HTTP ${res.status})`)\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const release = await fetchLatestRelease(includeBeta)\n} catch (e) {\n  if (e.message === 'Failed to fetch releases') {\n    showCachedReleaseNotes()\n  } else {\n    throw e\n  }\n}","preventionTips":["Cache the last successful releases response for offline fallback","Retry with exponential backoff on failure","Send a GitHub token to avoid anonymous rate limits","Surface res.status in error messages for faster diagnosis","Monitor X-RateLimit-Remaining before polling"],"tags":["network","github","http","api"],"backgroundTag":"http-error-response","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}