{"record":{"id":"370e276f6fe8db9a","repo":"stablyai/orca","slug":"github-request-failed-res-status-res-statuste-370e27","errorCode":null,"errorMessage":"GitHub request failed ${res.status} ${res.statusText}: ${body.slice(0, 300)}","messagePattern":"GitHub request failed (.+?) (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/scripts/verify-release-required-assets.mjs","lineNumber":60,"sourceCode":"      names.add(new URL(value).pathname.split('/').findLast(Boolean) ?? value)\n    } catch {\n      names.add(value.split('/').findLast(Boolean) ?? value)\n    }\n  }\n  return [...names]\n}\n\nasync function githubFetch(url, token, accept = 'application/vnd.github+json') {\n  const res = await fetch(url, {\n    headers: {\n      Accept: accept,\n      Authorization: `Bearer ${token}`,\n      'X-GitHub-Api-Version': API_VERSION\n    }\n  })\n  if (!res.ok) {\n    const body = await res.text().catch(() => '')\n    throw new Error(`GitHub request failed ${res.status} ${res.statusText}: ${body.slice(0, 300)}`)\n  }\n  return res\n}\n\nasync function fetchRelease(repo, tag, token) {\n  // The publish gate runs while the release is still draft.\n  const res = await githubFetch(`https://api.github.com/repos/${repo}/releases?per_page=100`, token)\n  const releases = await res.json()\n  if (!Array.isArray(releases)) {\n    throw new Error(`GitHub releases response for ${repo} was not an array`)\n  }\n  const release = releases.find((candidate) => candidate.tag_name === tag)\n  if (!release) {\n    throw new Error(`Release ${repo}@${tag} was not found in the draft-aware releases list`)\n  }\n  return release\n}\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/verify-release-required-assets.mjs#L42-L78","documentation":"Thrown by the release asset verification script's githubFetch helper when a GitHub API HTTP response has a non-ok status (!res.ok). The error includes the HTTP status code, status text, and the first 300 characters of the response body for diagnostics. The request uses Bearer token auth with the GitHub API version 2022-11-28 header. Common causes are authentication failures (401/403), rate limiting (403 with rate limit headers), not-found (404 for wrong repo/tag), or server errors (5xx).","triggerScenarios":"Any GitHub API call in the release verification flow (fetching releases list, fetching asset content) returns a non-2xx status. The token (from GH_TOKEN or GITHUB_TOKEN env var) may be expired, lack permissions for the repo, the repo name may be wrong, the rate limit may be exceeded, or GitHub may be experiencing an outage.","commonSituations":"An expired or revoked GitHub token in CI; insufficient token scopes (needs repo access for private repos or draft releases); GITHUB_REPOSITORY env var set to the wrong repo; GitHub API rate limit hit (60/hr unauthenticated, 5000/hr authenticated); the release tag doesn't exist yet (404); GitHub API is temporarily unavailable (5xx).","solutions":["Read the status code and body from the error message: 401/403 means auth or permission issues, 404 means wrong repo/tag, 403 with 'rate limit' means throttling.","Verify the token is valid and has not expired: echo $GH_TOKEN and test it with curl -H \"Authorization: Bearer $GH_TOKEN\" https://api.github.com/user.","Ensure the token has repo scope for private repositories and draft release access.","If rate-limited, wait for the reset window (check X-RateLimit-Reset header) or use a token with higher limits.","For 404 errors, verify GITHUB_REPOSITORY is set correctly and the tag exists in the releases list."],"exampleFix":"// before — no retry on transient failures\n//   const res = await githubFetch(url, token)\n//\n// after — add exponential backoff retry for 5xx and rate-limit responses\n//   async function githubFetchWithRetry(url, token, maxRetries = 3) {\n//     for (let attempt = 0; attempt <= maxRetries; attempt++) {\n//       const res = await fetch(url, { headers: { Authorization: `Bearer ${token}`, ... } })\n//       if (res.ok) return res\n//       if (res.status >= 500 || res.status === 429) {\n//         if (attempt === maxRetries) throw new Error(`GitHub request failed after ${maxRetries} retries: ${res.status}`)\n//         await new Promise(r => setTimeout(r, 2 ** attempt * 1000))\n//         continue\n//       }\n//       throw new Error(`GitHub request failed ${res.status}: ${(await res.text()).slice(0, 300)}`)\n//     }\n//   }","handlingStrategy":"retry","validationCode":"// Validate token and connectivity before making API calls.\nasync function preCheckGitHubAccess(token, repo) {\n  const res = await fetch('https://api.github.com/user', {\n    headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }\n  })\n  if (!res.ok) {\n    return { ok: false, message: `Token invalid or expired (HTTP ${res.status})` }\n  }\n  const user = await res.json()\n  return { ok: true, user: user.login }\n}","typeGuard":null,"tryCatchPattern":"// Retry on transient failures (5xx, rate limit); fail fast on auth/not-found.\nasync function githubFetchWithRetry(url, token, maxRetries = 3) {\n  for (let attempt = 0; attempt <= maxRetries; attempt++) {\n    const res = await fetch(url, {\n      headers: {\n        Accept: 'application/vnd.github+json',\n        Authorization: `Bearer ${token}`,\n        'X-GitHub-Api-Version': '2022-11-28'\n      }\n    })\n    if (res.ok) return res\n    const body = await res.text().catch(() => '')\n    // Retry only on server errors and rate limits\n    if (res.status >= 500 || res.status === 429) {\n      if (attempt === maxRetries) {\n        throw new Error(`GitHub request failed after ${maxRetries + 1} attempts: ${res.status} ${body.slice(0, 300)}`)\n      }\n      const retryAfter = parseInt(res.headers.get('Retry-After') || '2', 10)\n      await new Promise((r) => setTimeout(r, retryAfter * 1000))\n      continue\n    }\n    // Non-retryable: auth, not found, etc.\n    throw new Error(`GitHub request failed ${res.status} ${res.statusText}: ${body.slice(0, 300)}`)\n  }\n}","preventionTips":["Use a GitHub App token or long-lived PAT with appropriate scopes for CI; avoid short-lived tokens that expire mid-run.","Batch API requests where possible to stay under rate limits (the script already uses per_page=100 for releases).","Check rate limit headers (X-RateLimit-Remaining, X-RateLimit-Reset) in the response and log them for monitoring.","Verify GITHUB_REPOSITORY is set correctly in CI before the release verification step runs."],"tags":["github-api","network","authentication","rate-limit","release-verification","ci-gate"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}