{"record":{"id":"d918410f1e9ae441","repo":"vercel-labs/skills","slug":"downloaded-url-is-empty","errorCode":null,"errorMessage":"Downloaded URL is empty","messagePattern":"Downloaded URL is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/download-source.ts","lineNumber":270,"sourceCode":"  const { readdir } = await import('node:fs/promises');\n  const entries = await readdir(dir, { withFileTypes: true });\n  const visibleEntries = entries.filter((entry) => entry.name !== '__MACOSX');\n  if (visibleEntries.length !== 1 || !visibleEntries[0]!.isDirectory()) return null;\n  return join(dir, visibleEntries[0]!.name);\n}\n\nexport async function downloadSource(url: string): Promise<DownloadedSource> {\n  const limits = getDownloadLimits();\n  const tempDir = await mkdtemp(join(tmpdir(), 'skills-download-'));\n  const downloadedFile = join(tempDir, 'source.download');\n  const extractDir = join(tempDir, 'extract');\n\n  try {\n    await downloadToFile(url, downloadedFile, limits);\n\n    const downloadedStats = await stat(downloadedFile);\n    if (downloadedStats.size === 0) {\n      throw new Error('Downloaded URL is empty');\n    }\n\n    if (await isValidSkillMarkdown(downloadedFile)) {\n      const skillDir = join(tempDir, 'skill');\n      await mkdir(skillDir, { recursive: true });\n      await writeFile(join(skillDir, 'SKILL.md'), await readFile(downloadedFile));\n      return { rootDir: skillDir, tempDir, kind: 'skill-md' };\n    }\n\n    await mkdir(extractDir, { recursive: true });\n    if (await tryExtractArchive(downloadedFile, extractDir, limits)) {\n      const rootDir = (await getSingleTopLevelDirectory(extractDir)) ?? extractDir;\n      return { rootDir, tempDir, kind: 'archive' };\n    }\n\n    throw new Error('Downloaded URL is not a valid SKILL.md file or supported archive');\n  } catch (error) {\n    await rm(tempDir, { recursive: true, force: true }).catch(() => {});","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/download-source.ts#L252-L288","documentation":"Raised by downloadSource() when the HTTP download completes but the resulting file on disk is zero bytes. The library treats an empty body as a hard failure because neither SKILL.md parsing nor archive extraction can succeed on it.","triggerScenarios":"downloadToFile(url) succeeds (2xx) but the server returns an empty body — common with misconfigured redirects, 204/200-with-no-content endpoints, auth walls that redirect to a blank page, or truncated connections.","commonSituations":"Passing a URL that redirects (auth required, changed repo layout) to 'skills add <url>'; GitHub raw URLs for files that were renamed/deleted; CDN/proxy returning empty 200; typos in the URL path.","solutions":["curl -IL <url> the exact URL and check the final status code, redirect chain, and Content-Length","If it's a private repo asset, provide a token or use a public raw URL","Point at the raw file directly (e.g. raw.githubusercontent.com/.../SKILL.md) rather than an HTML page","Retry with a stable network if the transfer was truncated"],"exampleFix":"# before\nskills add https://example.com/my-skill   # redirects to empty login page\n# after\nskills add https://raw.githubusercontent.com/org/repo/main/SKILL.md","handlingStrategy":"retry","validationCode":"const size = await fetch(url, { method: 'HEAD' }).then((r) => Number(r.headers.get('content-length') ?? 0));\nif (size <= 0) throw new Error(`URL ${url} returns empty body; refusing to download`);","typeGuard":null,"tryCatchPattern":"let lastErr;\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { return await downloadSource(url); }\n  catch (e) {\n    lastErr = e;\n    if (!(e instanceof Error && /Downloaded URL is empty/.test(e.message))) throw e;\n    await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));\n  }\n}\nthrow lastErr;","preventionTips":["Verify URLs with curl -IL before wiring them into config","Prefer raw content URLs (raw.githubusercontent.com) over HTML pages","Add a HEAD-request size check before downloading in your own tooling"],"tags":["network","download","empty-response"],"backgroundTag":"empty-http-response-body","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}