{"record":{"id":"16b8437dfb24050c","repo":"upstash/context7","slug":"failed-to-fetch-item-path-fileresponse-statu","errorCode":null,"errorMessage":"Failed to fetch ${item.path}: ${fileResponse.status}","messagePattern":"Failed to fetch (.+?): (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/cli/src/utils/github.ts","lineNumber":286,"sourceCode":"        : \"\";\n    return { files: [], error: `GitHub API error: ${treeData.error}${hint}` };\n  }\n\n  const skillFiles = treeData.tree.filter(\n    (item) => item.type === \"blob\" && item.path.startsWith(skillPath + \"/\")\n  );\n\n  if (skillFiles.length === 0) {\n    return { files: [], error: `No files found in ${skillPath}` };\n  }\n\n  const files: SkillFile[] = [];\n  for (const item of skillFiles) {\n    const rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branch}/${item.path}`;\n    const fileResponse = await fetch(rawUrl, { headers: ghHeaders });\n\n    if (!fileResponse.ok) {\n      console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);\n      continue;\n    }\n\n    const content = await fileResponse.text();\n    const relativePath = item.path.slice(skillPath.length + 1);\n\n    // Reject paths that attempt directory traversal\n    if (relativePath.includes(\"..\")) {\n      console.warn(`Skipping file with unsafe path: ${item.path}`);\n      continue;\n    }\n\n    files.push({\n      path: relativePath,\n      content,\n    });\n  }\n","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/upstash/context7/blob/5284672feb575908efead6fcf1b5e542f8d607bb/packages/cli/src/utils/github.ts#L268-L304","documentation":"console.warn from the GitHub skill-file downloader in packages/cli/src/utils/github.ts: a raw.githubusercontent.com fetch for one file of the skill returned a non-OK HTTP status, so that file is skipped and the loop continues with the rest. Typical statuses are 404 (file vanished from the branch between the tree listing and the raw fetch, e.g. force-push), 403 (unauthenticated rate limit), and 5xx (raw CDN hiccup). The overall fetchSkillFiles call still succeeds, but the installed skill is missing files.","triggerScenarios":"Installing a skill from a repo whose branch was force-pushed/reorganized mid-download; unauthenticated GitHub API usage hitting the 60 req/hr rate limit (the tree listing succeeds, individual raw fetches get throttled); transient raw.githubports 5xx; a file path with URL-encodable characters fetched unencoded.","commonSituations":"Installing a fast-moving repo at the exact moment upstream reorganizes its skills folder; CI jobs installing many skills in a burst without a GITHUB_TOKEN; corporate networks with intercepting proxies returning odd status codes.","solutions":["Re-run the install — the tree listing is refreshed and the moved file resolves at its new path","If you saw 403s, wait out the unauthenticated rate-limit window (~1h) or supply a GitHub token via the CLI's supported env var to raise the limit","Verify the file still exists on the branch: open the raw URL from the repo in a browser","Pin/verify the skill repo is intact (no force-push in flight), then retry"],"exampleFix":"// before (utils/github.ts)\nconst fileResponse = await fetch(rawUrl, { headers: ghHeaders });\nif (!fileResponse.ok) {\n  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);\n  continue;\n}\n\n// after — retry once on 403/5xx before giving up\nlet fileResponse = await fetch(rawUrl, { headers: ghHeaders });\nif (fileResponse.status === 403 || fileResponse.status >= 500) {\n  await new Promise((r) => setTimeout(r, 1000));\n  fileResponse = await fetch(rawUrl, { headers: ghHeaders });\n}\nif (!fileResponse.ok) {\n  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);\n  continue;\n}","handlingStrategy":"retry","validationCode":"// Validate the raw-file URL is live before handing content downstream\nasync function fetchSkillFileSafe(rawUrl: string, headers: HeadersInit): Promise<string | null> {\n  for (let attempt = 0; attempt < 2; attempt++) {\n    const res = await fetch(rawUrl, { headers });\n    if (res.ok) return res.text();\n    if (res.status !== 403 && res.status < 500) {\n      console.warn(`Failed to fetch ${rawUrl}: ${res.status}`); // permanent — skip\n      return null;\n    }\n    await new Promise((r) => setTimeout(r, attempt * 1000 + 500)); // transient — retry\n  }\n  return null;\n}","typeGuard":"function isTransientHttpStatus(status: number): boolean {\n  return status === 403 || status === 408 || status === 429 || status >= 500;\n}","tryCatchPattern":"const fileResponse = await fetch(rawUrl, { headers: ghHeaders });\nif (!fileResponse.ok) {\n  if (isTransientHttpStatus(fileResponse.status)) {\n    // rate limit or CDN hiccup: back off and retry once before skipping\n    await new Promise((r) => setTimeout(r, 1500));\n    const retry = await fetch(rawUrl, { headers: ghHeaders });\n    if (retry.ok) return retry.text();\n  }\n  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);\n  continue;\n}","preventionTips":["Authenticate GitHub requests (token via supported env var) to stay clear of the 60 req/hr anonymous limit","Retry the whole install once before assuming a skill is broken — tree state may have settled","Check a missing file against the branch in the browser to distinguish 404 (permanent) from 403/5xx (transient)"],"tags":["network","http","github","rate-limit","skills"],"backgroundTag":"http-error-response","analyzedSha":"5284672feb575908efead6fcf1b5e542f8d607bb","analyzedAt":"2026-08-18T18:00:18.510Z","schemaVersion":2},"datasetVersion":"2026-08-24T22:17:12.610Z"}