{"record":{"id":"6893b0f326b0ca1a","repo":"nexu-io/open-design","slug":"download-url-failed-resp-status-resp-stat","errorCode":null,"errorMessage":"download ${url} failed: ${resp.status} ${resp.statusText}","messagePattern":"download (.+?) failed: (.+?) (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/daemon/src/community-pets-sync.ts","lineNumber":186,"sourceCode":"        description: pet.description ?? '',\n        spritesheetPath: 'spritesheet.webp',\n        author: pet.authorLabel,\n        authorXUrl: pet.authorXUrl,\n        source: 'j20-hatchery',\n        sourceUrl: pet.galleryUrl,\n      },\n      spritesheetUrl: pet.spritesheetUrl,\n      spritesheetExt: extOf(pet.spritesheetUrl),\n    });\n    if (limit && tasks.length >= limit) break;\n  }\n  return tasks;\n}\n\nasync function downloadBinary(url: string): Promise<Buffer> {\n  const resp = await fetch(url);\n  if (!resp.ok) {\n    throw new Error(`download ${url} failed: ${resp.status} ${resp.statusText}`);\n  }\n  const ab = await resp.arrayBuffer();\n  return Buffer.from(ab);\n}\n\nasync function writePet(\n  task: PetTask,\n  outRoot: string,\n  force: boolean,\n): Promise<'wrote' | 'skipped'> {\n  const dir = path.join(outRoot, task.folder);\n  const sheetPath = path.join(dir, `spritesheet.${task.spritesheetExt}`);\n  const manifestPath = path.join(dir, 'pet.json');\n  if (!force && (await pathExists(sheetPath)) && (await pathExists(manifestPath))) {\n    return 'skipped';\n  }\n  await mkdir(dir, { recursive: true });\n  const bytes = await downloadBinary(task.spritesheetUrl);","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/community-pets-sync.ts#L168-L204","documentation":"Thrown by downloadBinary when the HTTP GET to a spritesheet URL returns a non-2xx status. This function downloads the actual image binary for a pet spritesheet. The error includes the URL and HTTP status+statusText. Common causes: the spritesheet URL is a CDN link that has expired, the hosting service returned a 403/404, or a transient network error.","triggerScenarios":"During writePet, downloadBinary(task.spritesheetUrl) is called. The fetch returns non-ok (e.g., 403 Forbidden from an expired presigned URL, 404 for a removed asset, or 5xx from the CDN). The error fires at line 188.","commonSituations":"A PetShare spritesheet URL uses a Supabase presigned URL that expired. A Hatchery sprite was deleted from its CDN. Rate limiting from the image host. A CDN misconfiguration returns 403 for hotlinked images.","solutions":["Retry the download — transient CDN errors often resolve on retry.","Check if the spritesheet URL is still valid (not an expired presigned link).","If the asset is permanently gone, the pet entry should be skipped or marked as failed in the sync result.","Add a download timeout via AbortSignal to avoid hanging on unresponsive hosts."],"exampleFix":"// before — bare fetch with no timeout\nconst resp = await fetch(url);\nif (!resp.ok) throw new Error(`download ${url} failed: ${resp.status}`);\n\n// after — timeout and retry\nconst controller = new AbortController();\nconst timeout = setTimeout(() => controller.abort(), 15_000);\ntry {\n  const resp = await fetch(url, { signal: controller.signal });\n  if (!resp.ok) throw new Error(`download ${url} failed: ${resp.status} ${resp.statusText}`);\n  return Buffer.from(await resp.arrayBuffer());\n} finally {\n  clearTimeout(timeout);\n}","handlingStrategy":"retry","validationCode":"// Before downloading, validate the URL is well-formed and uses HTTPS.\nfunction isValidSpritesheetUrl(url: string): boolean {\n  try {\n    const parsed = new URL(url);\n    return parsed.protocol === 'https:' && parsed.pathname.length > 1;\n  } catch {\n    return false;\n  }\n}\n\n// Check before calling downloadBinary\nif (!isValidSpritesheetUrl(task.spritesheetUrl)) {\n  throw new Error(`Invalid spritesheet URL: ${task.spritesheetUrl}`);\n}","typeGuard":"export function isDownloadError(error: unknown): boolean {\n  return error instanceof Error && error.message.startsWith('download ') && error.message.includes('failed:');\n}","tryCatchPattern":"async function downloadWithRetry(url: string, maxAttempts = 3): Promise<Buffer> {\n  let lastError: unknown;\n  for (let attempt = 0; attempt < maxAttempts; attempt++) {\n    try {\n      return await downloadBinary(url);\n    } catch (error) {\n      lastError = error;\n      if (error instanceof Error && error.message.includes('failed:')) {\n        // HTTP error — retry with backoff for transient statuses\n        await new Promise(r => setTimeout(r, 1000 * (attempt + 1)));\n        continue;\n      }\n      throw error;\n    }\n  }\n  throw lastError;\n}","preventionTips":["Add a download timeout (AbortSignal) to avoid hanging on unresponsive hosts.","Retry transient HTTP errors (5xx, 429) with exponential backoff.","Validate URLs are HTTPS before downloading.","Surface per-pet download failures in the sync result rather than aborting the entire sync."],"tags":["network","external-api","community-pets","download","transient"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}