{"record":{"id":"0588246ad971d168","repo":"paperclipai/paperclip","slug":"cloud-readiness-run-listing-is-incomplete","errorCode":null,"errorMessage":"Cloud readiness run listing is incomplete.","messagePattern":"Cloud readiness run listing is incomplete\\.","errorType":"console","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/cloud-source-verification.mjs","lineNumber":31,"sourceCode":"  return run.workflow_id === workflowId && run.path === workflowPath &&\n    run.repository?.full_name === repository && run.head_repository?.full_name === repository &&\n    run.head_sha === sha && run.head_branch === \"master\" && run.event === \"push\" &&\n    Number.isSafeInteger(run.id) && run.id > 0 &&\n    Number.isSafeInteger(run.run_attempt) && run.run_attempt > 0;\n}\n\n// Consume one versioned job, independent of image/migrator availability. A\n// failed image build must not invalidate source checks that already passed.\nexport async function readSourceVerification(sha, api) {\n  assertSha(sha);\n  const workflow = await api(`/repos/${repository}/actions/workflows/cloud-readiness.yml`);\n  if (workflow.path !== workflowPath || !Number.isSafeInteger(workflow.id) || workflow.id < 1) {\n    throw new Error(\"Cloud readiness workflow identity does not match.\");\n  }\n  const listing = await api(`/repos/${repository}/actions/workflows/${workflow.id}/runs?head_sha=${sha}&event=push&branch=master&per_page=100`);\n  if (!Array.isArray(listing.workflow_runs) || !Number.isSafeInteger(listing.total_count) ||\n      listing.total_count < 0 || listing.total_count > 100 || listing.workflow_runs.length !== listing.total_count) {\n    throw new Error(\"Cloud readiness run listing is incomplete.\");\n  }\n  const run = listing.workflow_runs.filter((candidate) => trustedRun(candidate, sha, workflow.id))\n    .sort((a, b) => b.id - a.id)[0];\n  if (!run) return undefined;\n\n  // Attempt-specific jobs prevent an earlier successful attempt from blessing\n  // a later rerun. Keep pagination even though today's matrix fits one page.\n  const jobs = [];\n  for (let page = 1; page <= 10; page += 1) {\n    const batch = await api(`/repos/${repository}/actions/runs/${run.id}/attempts/${run.run_attempt}/jobs?per_page=100&page=${page}`);\n    if (!Array.isArray(batch.jobs)) throw new Error(\"Cloud readiness job listing is malformed.\");\n    jobs.push(...batch.jobs);\n    if (batch.jobs.length < 100) break;\n    if (page === 10) throw new Error(\"Cloud readiness job listing is incomplete.\");\n  }\n  const matches = jobs.filter((job) => job.name === sourceVerificationJob);\n  if (matches.length > 1) throw new Error(\"Cloud source verification job is ambiguous.\");\n  const job = matches[0];","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/cloud-source-verification.mjs#L13-L49","documentation":"readSourceVerification validates the GitHub Actions run listing for the cloud-readiness workflow before trusting it. This error means the response's workflow_runs array or total_count field failed the completeness invariant: missing/non-array workflow_runs, missing/non-integer/negative/over-100 total_count, or array length not equal to total_count. The library throws rather than blessing a release off a truncated or unexpected listing.","triggerScenarios":"Calling readSourceVerification (directly or via waitForSourceVerification) when the GET /repos/paperclipai/paperclip/actions/workflows/{id}/runs?head_sha=...&event=push&branch=master&per_page=100 response lacks workflow_runs, has a malformed or out-of-range total_count, or returns fewer runs than total_count (pagination beyond one page of 100).","commonSituations":"A mock or stubbed api() returns a partial fixture; GitHub changes the response envelope shape; more than 100 runs match head_sha=event=push&branch=master (listing genuinely overflows per_page=100); an API gateway returns HTML/empty JSON that decoded into an unexpected object.","solutions":["Confirm the workflow actually has runs for that sha on master with event=push; a wrong/incomplete sha yields no listing fields.","If more than 100 runs match, delete old workflow runs or adjust the query so the result fits one page (total_count <= 100 is required).","Inspect the raw JSON at https://api.github.com/repos/paperclipai/paperclip/actions/workflows/{id}/runs?head_sha=<sha>&event=push&branch=master to see the actual envelope.","If api() is injected (tests/scripts), fix the stub to return { total_count, workflow_runs: [...] } with matching lengths.","Rerun the release poll once GitHub API behavior is normal; the script retries transient transport errors but treats a bad listing as fatal."],"exampleFix":"// before: stubbed api returning only runs\nconst api = async () => ({ workflow_runs: [run] });\n// after\nconst api = async () => ({ total_count: 1, workflow_runs: [run] });","handlingStrategy":"validation","validationCode":"const listing = await api(path);\nif (!listing || !Array.isArray(listing.workflow_runs) || !Number.isSafeInteger(listing.total_count) || listing.total_count < 0 || listing.total_count > 100 || listing.workflow_runs.length !== listing.total_count) throw new Error('run listing incomplete');","typeGuard":"function isValidListing(l) {\n  return !!l && Array.isArray(l.workflow_runs) && Number.isSafeInteger(l.total_count) &&\n    l.total_count >= 0 && l.total_count <= 100 && l.workflow_runs.length === l.total_count;\n}","tryCatchPattern":"try {\n  const proof = await readSourceVerification(sha, api);\n} catch (e) {\n  if (e.message.includes('run listing is incomplete')) {\n    console.error('GitHub returned an unexpected runs envelope; check sha/branch filters and retry later.');\n  }\n}","preventionTips":["Keep the number of push runs per sha on master below 100 (the script requires total_count <= 100).","When stubbing api() in tests, always include total_count consistent with workflow_runs.length.","Pin checks to full 40-char shas so head_sha filters return exactly the intended runs.","Log the raw listing JSON when this error fires to spot API envelope changes early."],"tags":["github-actions","api-response-shape","pagination"],"backgroundTag":"unexpected-response-shape","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}