From 2386097be9e950c082d41ff596593841e2fd5236 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 3 Nov 2026 20:07:15 +0000 Subject: [PATCH] Migration readiness: retry transient GitHub API errors and show the network cause The runner on slow-fetch heads (48 commits of merged history) failed closed with a TypeError at the GitHub API stage. Reads now retry up to three times with a 30 s timeout; rule refusals are never retried. The failure line includes the cause code. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_012BMmZWrkFxXJddyVkAXF4c --- scripts/migration-pr-readiness.mjs | 18 +++++++++++++----- scripts/publish-migration-readiness.mjs | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/migration-pr-readiness.mjs b/scripts/migration-pr-readiness.mjs index 37461d1b36..3664fa1c9a 100644 --- a/scripts/migration-pr-readiness.mjs +++ b/scripts/migration-pr-readiness.mjs @@ -205,11 +205,19 @@ async function main() { const env = process.env requireFact(env.GITHUB_TOKEN, 'read-only metadata token required') const request = async (url, token, options = {}) => { - const res = await fetch(url, { ...options, headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, ...options.headers }, redirect: 'error', signal: AbortSignal.timeout(15000) }) - requireFact(res.ok, `proof read HTTP ${res.status}`) - const text = await res.text() - requireFact(text.length < 16 * 1024 * 1024, 'oversized proof response') - return JSON.parse(text) + // A transient network error (fetch failed, a stalled body) is retried; our own rule refusals never are. + for (let attempt = 1; ; attempt++) { + try { + const res = await fetch(url, { ...options, headers: { Accept: 'application/vnd.github+json', Authorization: `Bearer ${token}`, ...options.headers }, redirect: 'error', signal: AbortSignal.timeout(30000) }) + requireFact(res.ok, `proof read HTTP ${res.status}`) + const text = await res.text() + requireFact(text.length < 16 * 1024 * 1024, 'oversized proof response') + return JSON.parse(text) + } catch (error) { + if (error?.safe === true || attempt >= 3) throw error + await new Promise(resolve => setTimeout(resolve, attempt * 2000)) + } + } } const collectorArgs = { env, collectorSha256: hashBytes(readFileSync(process.argv[1])), event: JSON.parse(readFileSync(env.GITHUB_EVENT_PATH, 'utf8')), diff --git a/scripts/publish-migration-readiness.mjs b/scripts/publish-migration-readiness.mjs index f6828f44f0..4b5f114255 100644 --- a/scripts/publish-migration-readiness.mjs +++ b/scripts/publish-migration-readiness.mjs @@ -10,7 +10,7 @@ const files = ['scripts/migration-pr-readiness.mjs', 'scripts/publish-migration- export function describeFailure(error) { if (error?.safe === true) return `rule refused: ${error.message}` const part = value => ((typeof value === 'string' || typeof value === 'number') && /^[A-Za-z0-9_.:-]{1,40}$/.test(String(value)) ? String(value) : '?') - return `error ${part(error?.name)} code=${part(error?.code)} status=${part(error?.status)} stage=${part(error?.stage)}` + return `error ${part(error?.name)} code=${part(error?.code ?? error?.cause?.code)} status=${part(error?.status)} stage=${part(error?.stage)}` } export async function publishReadiness({ env, event, api, git, publisherSha256, evaluate }) { requireFact(env.GITHUB_ACTIONS === 'true' && env.GITHUB_EVENT_NAME === 'pull_request_target' && env.GITHUB_REPOSITORY === repo && env.GITHUB_WORKFLOW_REF === `${repo}/.github/workflows/migration-readiness.yml@refs/heads/main`, 'trusted workflow required')