{"record":{"id":"b9ecefa148ebe62b","repo":"paperclipai/paperclip","slug":"github-actions-read-failed-http-response-status","errorCode":null,"errorMessage":"GitHub Actions read failed (HTTP ${response.status}).","messagePattern":"GitHub Actions read failed \\(HTTP (.+?)\\)\\.","errorType":"console","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/cloud-source-verification.mjs","lineNumber":130,"sourceCode":"        if (!retryable) throw new Error(`GitHub Actions read failed: ${cause.message}`);\n        log(`GitHub Actions read failed (${cause.message}); retrying (${attempt}/${attempts - 1}).`);\n        await pause();\n        continue;\n      }\n      if (response.ok) {\n        try {\n          // A 200 whose body is truncated or undecodable is a transport\n          // failure like any other, so it belongs inside the retry.\n          return await response.json();\n        } catch (cause) {\n          if (!retryable) throw new Error(`GitHub Actions read failed: ${cause.message}`);\n          log(`GitHub Actions read body failed (${cause.message}); retrying (${attempt}/${attempts - 1}).`);\n          await pause();\n          continue;\n        }\n      }\n      if (!retryable || !(TRANSIENT_READ_STATUSES.has(response.status) || rateLimited(response))) {\n        throw new Error(`GitHub Actions read failed (HTTP ${response.status}).`);\n      }\n      log(`GitHub Actions read failed (HTTP ${response.status}); retrying (${attempt}/${attempts - 1}).`);\n      await pause(response);\n    }\n  };\n}\n\nexport async function waitForSourceVerification(sha, {\n  api, now = Date.now, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n  timeoutMs = 45 * 60_000, intervalMs = 30_000, log = console.log,\n} = {}) {\n  assertSha(sha);\n  const deadline = now() + timeoutMs;\n  log(`Waiting for ${sourceVerificationJob} for ${sha}.`);\n  while (now() < deadline) {\n    const result = await readSourceVerification(sha, api);\n    if (result) return result;\n    const remaining = deadline - now();","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/cloud-source-verification.mjs#L112-L148","documentation":"When GitHub returns a non-OK status that is neither transient (408/425/429/500/502/503/504) nor a rate-limited 403 — or when retries are exhausted — the reader fails immediately with the HTTP status. A wrong token or a permissions problem should fail at once instead of waiting out retries.","triggerScenarios":"Any GET to api.github.com from createActionsReader returning e.g. 401 (bad/expired GITHUB_TOKEN), 403 without retry-after/x-ratelimit-remaining:0 headers (token lacks Actions read), 404 (repository or workflow not visible to the token), when retryable is false, or a transient status on the final attempt.","commonSituations":"GITHUB_TOKEN set to a PAT without the Actions:read permission; token from the wrong account/org; fine-grained PAT not granted to paperclipai/paperclip; token revoked or expired; secondary 403 that lacks rate-limit headers being treated as fatal.","solutions":["Regenerate/export GITHUB_TOKEN as a classic PAT with the repo scope or a fine-grained PAT with Actions: read on paperclipai/paperclip.","Read the status in the message: 401/403 -> token validity/permissions; 404 -> token cannot see the repo/workflow.","Confirm with: curl -sS -o /dev/null -w '%{http_code}' -H \"Authorization: Bearer $GITHUB_TOKEN\" https://api.github.com/repos/paperclipai/paperclip/actions/workflows/cloud-readiness.yml","If it was a 403 that looked like rate limiting but lacked headers, wait for the rate-limit window and check x-ratelimit-reset.","Rerun the release poll once the token/status issue is fixed."],"exampleFix":"// before: token without Actions read\nexport GITHUB_TOKEN=ghp_only_contents_read\n# after: classic PAT with repo scope or fine-grained PAT with Actions: read\nexport GITHUB_TOKEN=ghp_with_actions_read","handlingStrategy":"try-catch","validationCode":"// preflight: confirm the token can read Actions for the repo\nconst res = await fetch('https://api.github.com/repos/paperclipai/paperclip/actions/workflows/cloud-readiness.yml', { headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } });\nif (res.status === 401 || res.status === 403 || res.status === 404) throw new Error(`Token cannot read Actions (HTTP ${res.status})`);","typeGuard":null,"tryCatchPattern":"try {\n  const proof = await waitForSourceVerification(sha, { api, timeoutMs });\n} catch (e) {\n  const m = e.message.match(/GitHub Actions read failed \\(HTTP (\\d+)\\)/);\n  if (m) {\n    const hints = { 401: 'invalid/expired token', 403: 'token lacks Actions read or is blocked', 404: 'token cannot see the repo' };\n    console.error(`Fix token: ${hints[m[1]] ?? 'unexpected status ' + m[1]}`);\n  }\n}","preventionTips":["Issue the PAT with Actions: read (fine-grained) or repo scope (classic) for paperclipai/paperclip.","Rotate tokens before expiry; treat 401 as 'rotate now'.","Distinguish rate-limited 403 (has retry-after / x-ratelimit-remaining: 0, retried) from permission 403 (fails fast) — check headers when diagnosing.","Preflight the workflow endpoint with curl before starting a release poll."],"tags":["github-api","http","authentication","permissions"],"backgroundTag":"http-error-status","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}