paperclipai/paperclip · error · Error

GitHub Actions read failed

Error message

GitHub Actions read failed: ${cause.message}

What it means

createActionsReader wraps every fetch to api.github.com. When the fetch itself rejects (network error, DNS failure, 30s AbortSignal timeout, TLS error) and the attempt is not retryable — retry budget exhausted (attempt >= attempts, default 4) or the caller's deadline has passed — it throws this error carrying the underlying cause message. Retrying attempts log instead of throwing.

Solutions

  1. Check general connectivity: curl -sS https://api.github.com/rate_limit with your token.
  2. Check the GitHub status page for an ongoing Actions/API incident and retry after it clears.
  3. If behind a proxy, configure HTTPS_PROXY/node proxy support so fetch can reach api.github.com.
  4. Rerun the release poll — it is a transport problem, not a verdict on the source; a later run usually succeeds.
  5. If it fires near the poll timeout, that is by design (the reader stops retrying past the deadline); rerun to get a fresh 45-minute window.

Example fix

// before: no proxy configured
const api = createActionsReader({ token });
// after: run with proxy env
// HTTPS_PROXY=http://proxy.corp:8080 node scripts/cloud-source-verification.mjs <sha>
const api = createActionsReader({ token, attempts: 6 });
Defensive patterns

Strategy: retry

Validate before calling

// preflight connectivity + auth before the long poll
const res = await fetch('https://api.github.com/rate_limit', { headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } });
if (!res.ok) throw new Error(`GitHub unreachable or token invalid (HTTP ${res.status})`);

Try / catch

try {
  const proof = await waitForSourceVerification(sha, { api, timeoutMs });
} catch (e) {
  if (e.message.startsWith('GitHub Actions read failed:')) {
    console.error(`Transport error: ${e.message}. Check network/proxy and rerun the poll.`);
  }
}

Prevention

When it happens

Trigger: Network outage, offline machine, DNS failure, request exceeding AbortSignal.timeout(30_000), or the 4-attempt retry budget/45-minute polling deadline exhausted during repeated transport failures.

Common situations: Corporate proxy/firewall blocking api.github.com; GitHub Actions outage; running the script without network access; long CI poll where repeated transient errors exhaust retries near the deadline.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/3bb2f75dc4eb6062. Report an issue: GitHub.

Appendix: source

Thrown at scripts/cloud-source-verification.mjs:112

  token, fetchImpl = fetch, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
  attempts = 4, backoffMs = 1_000, log = console.log,
  now = Date.now, deadlineAt = () => Infinity, maxRetryAfterMs = 60_000,
} = {}) {
  return async (path) => {
    for (let attempt = 1; ; attempt += 1) {
      // Retry only while both the attempt budget and the caller's deadline
      // leave room for the wait this attempt would cost.
      const waitMs = backoffMs * attempt;
      const retryable = attempt < attempts && now() + waitMs < deadlineAt();
      const pause = (response) => sleep(retryAfterMs(response, maxRetryAfterMs) ?? waitMs);
      let response;
      try {
        response = await fetchImpl(`https://api.github.com${path}`, {
          headers: { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" },
          signal: AbortSignal.timeout(30_000), redirect: "error",
        });
      } catch (cause) {
        if (!retryable) throw new Error(`GitHub Actions read failed: ${cause.message}`);
        log(`GitHub Actions read failed (${cause.message}); retrying (${attempt}/${attempts - 1}).`);
        await pause();
        continue;
      }
      if (response.ok) {
        try {
          // A 200 whose body is truncated or undecodable is a transport
          // failure like any other, so it belongs inside the retry.
          return await response.json();
        } catch (cause) {
          if (!retryable) throw new Error(`GitHub Actions read failed: ${cause.message}`);
          log(`GitHub Actions read body failed (${cause.message}); retrying (${attempt}/${attempts - 1}).`);
          await pause();
          continue;
        }
      }
      if (!retryable || !(TRANSIENT_READ_STATUSES.has(response.status) || rateLimited(response))) {
        throw new Error(`GitHub Actions read failed (HTTP ${response.status}).`);

View on GitHub (pinned to 3f1d897a7c)