heygen-com/hyperframes · error

Malformed skills manifest from ${sourceLabel}

Error message

Malformed skills manifest from ${sourceLabel}

What it means

Thrown by asSkillsManifest when a fetched JSON payload does not narrow to a valid SkillsManifest — specifically when the top-level value is not an object, or when its skills field is missing, not an object, or null. This guards against a CDN or GitHub raw endpoint serving an error page or HTML redirect as a 200 response, which would otherwise cause a cryptic crash later in diffSkills.

Source

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

  for (let i = 0; i < 16; i++) {
    const p = join(dir, MANIFEST_FILE);
    if (existsSync(p)) return p;
    const parent = join(dir, "..");
    if (parent === dir) break;
    dir = parent;
  }
  return null;
}

/**
 * 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

View on GitHub (pinned to c2996c8626)

Solutions

  1. Check the sourceLabel in the error — it contains the exact URL that served the bad payload.
  2. Open the URL in a browser to verify it returns valid JSON with a top-level skills object.
  3. If using a custom repo/source, verify the skills-manifest.json exists at the repo root or that a skills/ directory is present.
  4. Fall back to the default source by omitting the --source flag.
Defensive patterns

Strategy: type-guard

Type guard

function isSkillsManifest(data: unknown): data is SkillsManifest {
  return (
    data !== null &&
    typeof data === "object" &&
    typeof (data as SkillsManifest).skills === "object" &&
    (data as SkillsManifest).skills !== null
  );
}

Try / catch

try {
  const manifest = await fetchRemoteManifest(source);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Malformed skills manifest")) {
    // Fall back to a local/bundled manifest or skip the update
    console.warn(`Remote manifest malformed, falling back to local: ${err.message}`);
    return resolveLocalManifest(fallbackPath);
  }
  throw err;
}

Prevention

When it happens

Trigger: A remote manifest URL (GitHub raw, CDN) returns 200 but with HTML content (error page, redirect page) instead of JSON; a manifest that was manually edited and its skills field was deleted or corrupted; the JSON parsed successfully but the structure doesn't have a skills object.

Common situations: GitHub raw.githubusercontent.com serving a stale or redirected page after a repo rename; a custom manifest URL pointing to the wrong file; network interception (corporate proxy) injecting an HTML page; a manifest format change in a newer repo version that this CLI doesn't understand.

Understand the failure class

Related errors


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