{"record":{"id":"0ee5f3e5bad5d13a","repo":"stablyai/orca","slug":"github-request-failed-res-status-res-statuste-0ee5f3","errorCode":null,"errorMessage":"GitHub request failed ${res.status} ${res.statusText}: ${body.slice(0, 300)}","messagePattern":"GitHub request failed (.+?) (.+?): (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/publish-complete-draft-releases.mjs","lineNumber":59,"sourceCode":"    return gitOutput(['rev-parse', `${tagCommit}^`], cwd) === currentCommit\n  } catch {\n    return false\n  }\n}\n\nasync function githubJson(fetchImpl, url, token, options = {}) {\n  const res = await fetchImpl(url, {\n    ...options,\n    headers: {\n      Accept: 'application/vnd.github+json',\n      Authorization: `Bearer ${token}`,\n      'X-GitHub-Api-Version': API_VERSION,\n      ...options.headers\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.json()\n}\n\nasync function fetchReleases(repo, token, fetchImpl) {\n  const releases = await githubJson(\n    fetchImpl,\n    `https://api.github.com/repos/${repo}/releases?per_page=100`,\n    token\n  )\n  if (!Array.isArray(releases)) {\n    throw new Error(`GitHub releases response for ${repo} was not an array`)\n  }\n  return releases\n}\n\nexport async function publishCompleteDraftReleases({\n  repo,","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/publish-complete-draft-releases.mjs#L41-L77","documentation":"Thrown by the githubJson helper in config/scripts/publish-complete-draft-releases.mjs after a fetch to the GitHub REST API returns a non-2xx status (`!res.ok`). The message embeds the HTTP status code, status text, and the first 300 characters of the response body so the failure is diagnosable from the error alone. It surfaces every GitHub API failure the script makes — listing releases, verifying assets, and PATCHing a draft to published.","triggerScenarios":"Call publishCompleteDraftReleases (or fetchReleases / the PATCH at line 119) when the GitHub API responds with: 401 (missing/invalid token), 403 (rate limit or insufficient token scope), 404 (wrong repo slug in GITHUB_REPOSITORY), 422 (malformed PATCH body), or any 5xx during a GitHub incident. The body slice distinguishes a rate-limit message from a scope error.","commonSituations":"CI runs without GH_TOKEN/GITHUB_TOKEN set correctly, a fine-grained PAT missing `contents:write`, hitting the secondary rate limit during a burst of release cuts, or a typo'd GITHUB_REPOSITORY value. GitHub 5xx outages during release windows also produce this.","solutions":["Inspect the embedded status: 401/403 → fix GH_TOKEN/GITHUB_TOKEN and its scopes; 404 → verify GITHUB_REPOSITORY slug; 5xx → wait and retry.","For 403 secondary-rate-limit or 5xx, retry with exponential backoff (these are transient).","Ensure the token has `contents:write` and `repo` scope for private repos before the run."],"exampleFix":"// before\nconst releases = await githubJson(fetchImpl, url, token)\n\n// after\nasync function githubJsonRetry(fetchImpl, url, token, options, retries = 3) {\n  for (let attempt = 0; ; attempt++) {\n    try {\n      return await githubJson(fetchImpl, url, token, options)\n    } catch (err) {\n      const m = /GitHub request failed (\\d+)/.exec(err.message)\n      const status = m ? Number(m[1]) : 0\n      const transient = status === 403 || status >= 500\n      if (attempt < retries && transient) {\n        await new Promise((r) => setTimeout(r, 2 ** attempt * 1000))\n        continue\n      }\n      throw err\n    }\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Before calling publishCompleteDraftReleases, sanity-check inputs\nif (!token) throw new Error('GH_TOKEN/GITHUB_TOKEN not set')\nif (!/^[-.\\w]+\\/.+$/.test(repo)) throw new Error(`Invalid repo slug: ${repo}`)","typeGuard":null,"tryCatchPattern":"try {\n  await publishCompleteDraftReleases({ repo, token })\n} catch (err) {\n  const m = /GitHub request failed (\\d+)/.exec(err.message)\n  const status = m ? Number(m[1]) : 0\n  if ((status === 403 || status >= 500) && attempt < maxRetries) {\n    await backoff(attempt)\n    continue\n  }\n  throw err\n}","preventionTips":["Provide GH_TOKEN with `contents:write` scope for the target repo.","Retry 403 secondary-rate-limit and 5xx responses with exponential backoff.","Parse the embedded status from the error message to branch retry vs. fail."],"tags":["github","api","network","release","auth"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}