paperclipai/paperclip · error · Error
GitHub Actions read failed
Error message
GitHub Actions read failed (HTTP ${response.status}). What it means
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.
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.
Example fix
// before: token without Actions read export GITHUB_TOKEN=ghp_only_contents_read # after: classic PAT with repo scope or fine-grained PAT with Actions: read export GITHUB_TOKEN=ghp_with_actions_read
Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: confirm the token can read Actions for the repo
const res = await fetch('https://api.github.com/repos/paperclipai/paperclip/actions/workflows/cloud-readiness.yml', { headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } });
if (res.status === 401 || res.status === 403 || res.status === 404) throw new Error(`Token cannot read Actions (HTTP ${res.status})`); Try / catch
try {
const proof = await waitForSourceVerification(sha, { api, timeoutMs });
} catch (e) {
const m = e.message.match(/GitHub Actions read failed \(HTTP (\d+)\)/);
if (m) {
const hints = { 401: 'invalid/expired token', 403: 'token lacks Actions read or is blocked', 404: 'token cannot see the repo' };
console.error(`Fix token: ${hints[m[1]] ?? 'unexpected status ' + m[1]}`);
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- github_webhook_recovery_http
- | null)?.error ?? `Failed to load profile ( )`}
- ACPX runtime executable must be a bounded executable file
- Agent identity is required
- Announcement request failed
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/b9ecefa148ebe62b.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/cloud-source-verification.mjs:130
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}).`);
}
log(`GitHub Actions read failed (HTTP ${response.status}); retrying (${attempt}/${attempts - 1}).`);
await pause(response);
}
};
}
export async function waitForSourceVerification(sha, {
api, now = Date.now, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
timeoutMs = 45 * 60_000, intervalMs = 30_000, log = console.log,
} = {}) {
assertSha(sha);
const deadline = now() + timeoutMs;
log(`Waiting for ${sourceVerificationJob} for ${sha}.`);
while (now() < deadline) {
const result = await readSourceVerification(sha, api);
if (result) return result;
const remaining = deadline - now();View on GitHub (pinned to 3f1d897a7c)