heygen-com/hyperframes · error

HTTP ${res.status} fetching ${url}

Error message

HTTP ${res.status} fetching ${url}

What it means

Thrown by fetchManifest when the HTTP response from a remote skills manifest URL has a non-OK status code. Unlike the malformed-manifest guard, this fires before JSON parsing — the server itself rejected the request. The URL and status code are included in the message.

Source

Thrown at packages/cli/src/utils/skillsManifest.ts:647

/**
 * Narrow an untrusted JSON payload to a SkillsManifest, or throw a clear error.
 * Guards against a CDN serving an error page (or a malformed manifest) as 200 —
 * without this, a bad shape surfaces later as a cryptic crash in diffSkills.
 */
function asSkillsManifest(data: unknown, sourceLabel: string): SkillsManifest {
  const m = data as Partial<SkillsManifest> | null;
  if (!m || typeof m !== "object" || typeof m.skills !== "object" || m.skills === null) {
    throw new Error(`Malformed skills manifest from ${sourceLabel}`);
  }
  return m as SkillsManifest;
}

async function fetchManifest(url: string): Promise<SkillsManifest> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
  try {
    const res = await fetch(url, { signal: controller.signal, headers: { Connection: "close" } });
    if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
    return asSkillsManifest(await res.json(), url);
  } finally {
    clearTimeout(timeout);
  }
}

/**
 * Resolve main's live HEAD sha via `git ls-remote`. GitHub's branch-raw CDN
 * (raw.githubusercontent.com/<owner>/<repo>/main/...) can serve stale content
 * for minutes after a push; a SHA-pinned raw URL is immediately consistent.
 * Returns null when git/network is unavailable so callers fall back to main.
 */
async function remoteHeadSha(repoSlug: string): Promise<string | null> {
  try {
    const { stdout } = await execFileAsync(
      "git",
      ["ls-remote", `https://github.com/${repoSlug}.git`, "refs/heads/main"],
      { timeout: FETCH_TIMEOUT_MS, env: { ...process.env, GIT_TERMINAL_PROMPT: "0" } },

View on GitHub (pinned to c2996c8626)

Solutions

  1. Check the status code in the error — 404 means wrong path/repo, 403 means private or rate-limited, 5xx means server error.
  2. Verify the URL is correct by opening it in a browser.
  3. If rate-limited by GitHub, wait a few minutes and retry, or use a local manifest source.
  4. Fall back to the default repo by omitting the custom source.
Defensive patterns

Strategy: retry

Try / catch

async function fetchManifestWithRetry(url: string, maxRetries = 3): Promise<SkillsManifest> {
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fetchManifest(url);
    } catch (err) {
      if (err instanceof Error && err.includes("HTTP 5") && i < maxRetries - 1) {
        await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
        continue;
      }
      throw err;
    }
  }
  throw new Error(`Failed after ${maxRetries} retries`);
}

Prevention

When it happens

Trigger: The manifest URL returns 404 (wrong path or repo), 403 (private repo without access), 5xx (server error), or any other non-2xx status. The fetch includes an AbortController with FETCH_TIMEOUT_MS, so a timeout triggers an AbortError (a different error), not this.

Common situations: Specifying a wrong owner/repo slug; the repo was renamed or made private; GitHub rate-limiting returns 403; the manifest path changed in a newer version of the skills repo; temporary CDN outage.

Related errors


AI-assisted analysis of heygen-com/hyperframes@c2996c8626 (2026-08-12). Data as JSON: /api/errors/efc87afd778ddf70. Report an issue: GitHub.