upstash/context7 · warning

Failed to fetch ${item.path}: ${fileResponse.status}

Error message

Failed to fetch ${item.path}: ${fileResponse.status}

What it means

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.

Source

Thrown at packages/cli/src/utils/github.ts:286

        : "";
    return { files: [], error: `GitHub API error: ${treeData.error}${hint}` };
  }

  const skillFiles = treeData.tree.filter(
    (item) => item.type === "blob" && item.path.startsWith(skillPath + "/")
  );

  if (skillFiles.length === 0) {
    return { files: [], error: `No files found in ${skillPath}` };
  }

  const files: SkillFile[] = [];
  for (const item of skillFiles) {
    const rawUrl = `${GITHUB_RAW}/${owner}/${repo}/${branch}/${item.path}`;
    const fileResponse = await fetch(rawUrl, { headers: ghHeaders });

    if (!fileResponse.ok) {
      console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);
      continue;
    }

    const content = await fileResponse.text();
    const relativePath = item.path.slice(skillPath.length + 1);

    // Reject paths that attempt directory traversal
    if (relativePath.includes("..")) {
      console.warn(`Skipping file with unsafe path: ${item.path}`);
      continue;
    }

    files.push({
      path: relativePath,
      content,
    });
  }

View on GitHub (pinned to 5284672feb)

Solutions

  1. Re-run the install — the tree listing is refreshed and the moved file resolves at its new path
  2. 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
  3. Verify the file still exists on the branch: open the raw URL from the repo in a browser
  4. Pin/verify the skill repo is intact (no force-push in flight), then retry

Example fix

// before (utils/github.ts)
const fileResponse = await fetch(rawUrl, { headers: ghHeaders });
if (!fileResponse.ok) {
  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);
  continue;
}

// after — retry once on 403/5xx before giving up
let fileResponse = await fetch(rawUrl, { headers: ghHeaders });
if (fileResponse.status === 403 || fileResponse.status >= 500) {
  await new Promise((r) => setTimeout(r, 1000));
  fileResponse = await fetch(rawUrl, { headers: ghHeaders });
}
if (!fileResponse.ok) {
  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);
  continue;
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate the raw-file URL is live before handing content downstream
async function fetchSkillFileSafe(rawUrl: string, headers: HeadersInit): Promise<string | null> {
  for (let attempt = 0; attempt < 2; attempt++) {
    const res = await fetch(rawUrl, { headers });
    if (res.ok) return res.text();
    if (res.status !== 403 && res.status < 500) {
      console.warn(`Failed to fetch ${rawUrl}: ${res.status}`); // permanent — skip
      return null;
    }
    await new Promise((r) => setTimeout(r, attempt * 1000 + 500)); // transient — retry
  }
  return null;
}

Type guard

function isTransientHttpStatus(status: number): boolean {
  return status === 403 || status === 408 || status === 429 || status >= 500;
}

Try / catch

const fileResponse = await fetch(rawUrl, { headers: ghHeaders });
if (!fileResponse.ok) {
  if (isTransientHttpStatus(fileResponse.status)) {
    // rate limit or CDN hiccup: back off and retry once before skipping
    await new Promise((r) => setTimeout(r, 1500));
    const retry = await fetch(rawUrl, { headers: ghHeaders });
    if (retry.ok) return retry.text();
  }
  console.warn(`Failed to fetch ${item.path}: ${fileResponse.status}`);
  continue;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of upstash/context7@5284672feb (2026-08-18). Data as JSON: /api/errors/16b8437dfb24050c. Report an issue: GitHub.