{"record":{"id":"998da52f7d67b6b3","repo":"midudev/autoskills","slug":"tarball-fetch-failed-res-status-url","errorCode":null,"errorMessage":"Tarball fetch failed: ${res.status} ${url}","messagePattern":"Tarball fetch failed: (.+?) (.+?)","errorType":"http","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/autoskills/scripts/sync-skills.mjs","lineNumber":220,"sourceCode":"    sizeKB: body.size || 0,\n  };\n}\n\n// Tarball download size threshold (KB). Above this we use per-file fetch.\nconst HEAVY_REPO_KB = 50_000;\n// Hard timeout for tarball downloads. Some repos have slow codeload CDNs.\nconst TARBALL_TIMEOUT_MS = 180_000;\n\nasync function downloadTarball(repo, sha, destFile) {\n  const url = `https://codeload.github.com/${repo}/tar.gz/${sha}`;\n  const headers = { \"User-Agent\": \"autoskills-sync\" };\n  if (GITHUB_TOKEN) headers.Authorization = `Bearer ${GITHUB_TOKEN}`;\n  const ac = new AbortController();\n  const timer = setTimeout(() => ac.abort(), TARBALL_TIMEOUT_MS);\n  try {\n    const res = await fetch(url, { headers, signal: ac.signal });\n    if (!res.ok || !res.body) {\n      throw new Error(`Tarball fetch failed: ${res.status} ${url}`);\n    }\n    await pipeline(res.body, createWriteStream(destFile));\n  } finally {\n    clearTimeout(timer);\n  }\n}\n\nasync function fetchRepoTree(repo, sha) {\n  const res = await ghFetch(`https://api.github.com/repos/${repo}/git/trees/${sha}?recursive=1`);\n  const body = await res.json();\n  if (body.truncated) {\n    throw new Error(`git tree truncated for ${repo}@${sha.slice(0, 7)}`);\n  }\n  return body.tree || [];\n}\n\nfunction findSkillDirsInTree(tree, skillName) {\n  // Returns array of { dir } where dir/SKILL.md exists in the tree.","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/midudev/autoskills/blob/0ec725320d2137253ab2e68e7ba8a072148e741a/packages/autoskills/scripts/sync-skills.mjs#L202-L238","documentation":"downloadTarball fetches a GitHub codeload tarball URL over HTTP and streams it to a file. If the response is not ok (non-2xx) or has no body, it throws this error including the HTTP status and URL. Common statuses: 404 (repo/branch/tag gone), 403 (rate limit), 5xx (GitHub server error).","triggerScenarios":"fetch(url) returns res.ok === false or res.body === null while downloading a repo tarball — 404 for missing ref, 403 rate-limited without/with an exhausted GITHUB_TOKEN, 5xx from GitHub.","commonSituations":"Hitting GitHub's unauthenticated rate limit during bulk syncs; syncing a repo branch/tag that was deleted; transient GitHub 5xx; GITHUB_TOKEN expired or revoked.","solutions":["Re-run after checking the URL in a browser — if 404, the ref/repo no longer exists","Set a valid GITHUB_TOKEN to raise the API/codeload rate limit; if 403, wait for the rate-limit reset","Check https://www.githubstatus.com for GitHub outages on 5xx","If status is from an abort (timeout), raise TARBALL_TIMEOUT_MS for large repos"],"exampleFix":"// before\n// no token -> 403 rate limited\nawait downloadTarball(url, dest);\n// after\nprocess.env.GITHUB_TOKEN = \"ghp_...\"; // authenticated requests\nawait downloadTarball(url, dest);","handlingStrategy":"retry","validationCode":"// pre-check the tarball URL responds 200\nconst probe = await fetch(url, { method: \"HEAD\", headers: GITHUB_TOKEN ? { Authorization: `Bearer ${GITHUB_TOKEN}` } : {} });\nif (!probe.ok) throw new Error(`tarball URL not fetchable: ${probe.status} ${url}`);","typeGuard":null,"tryCatchPattern":"async function downloadWithRetry(url, dest, attempts = 3) {\n  for (let i = 0; i < attempts; i++) {\n    try { return await downloadTarball(url, dest); }\n    catch (err) {\n      if (!err.message.startsWith(\"Tarball fetch failed\")) throw err;\n      const status = parseInt(err.message.match(/(\\d{3})/)?.[1] ?? \"0\", 10);\n      if (status === 404) throw err; // permanent\n      await new Promise(r => setTimeout(r, 2 ** i * 1000));\n    }\n  }\n  throw new Error(`tarball download failed after ${attempts} attempts`);\n}","preventionTips":["Set GITHUB_TOKEN to avoid unauthenticated rate limits (403)","Verify the branch/tag still exists before syncing","Handle 404 as permanent and 403/5xx as retryable","Monitor https://www.githubstatus.com during CI runs"],"tags":["http","network","github","download"],"backgroundTag":"http-error-response","analyzedSha":"0ec725320d2137253ab2e68e7ba8a072148e741a","analyzedAt":"2026-09-15T14:12:31.090Z","contentChangedAt":"2026-09-15T14:12:31.090Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}