{"record":{"id":"967643ab1ac689b4","repo":"jackwener/OpenCLI","slug":"download-error","errorCode":"DOWNLOAD_ERROR","errorMessage":"DOWNLOAD_ERROR","messagePattern":"DOWNLOAD_ERROR","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/yollomi/utils.js","lineNumber":98,"sourceCode":"    if (!fs.existsSync(resolved)) {\n        throw new CliError('FILE_NOT_FOUND', `File not found: ${resolved}`, 'Provide a valid file path or URL');\n    }\n    const ext = path.extname(resolved).toLowerCase();\n    const mimeMap = {\n        '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',\n        '.png': 'image/png', '.gif': 'image/gif',\n        '.webp': 'image/webp', '.bmp': 'image/bmp',\n    };\n    const mime = mimeMap[ext] || 'image/png';\n    const data = fs.readFileSync(resolved);\n    return `data:${mime};base64,${data.toString('base64')}`;\n}\nexport async function downloadOutput(url, outputDir, filename) {\n    fs.mkdirSync(outputDir, { recursive: true });\n    const destPath = path.join(outputDir, filename);\n    const resp = await fetch(url);\n    if (!resp.ok)\n        throw new CliError('DOWNLOAD_ERROR', `Download failed: HTTP ${resp.status}`, 'URL may have expired');\n    const buffer = Buffer.from(await resp.arrayBuffer());\n    fs.writeFileSync(destPath, buffer);\n    return { path: destPath, size: buffer.length };\n}\nexport function fmtBytes(bytes) {\n    if (bytes === 0)\n        return '0 B';\n    const k = 1024;\n    const sizes = ['B', 'KB', 'MB', 'GB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;\n}\n/** Per-model API route mapping (matches frontend model.apiEndpoint). */\nexport const MODEL_ROUTES = {\n    'flux': '/api/ai/flux',\n    'flux-schnell': '/api/ai/flux-schnell',\n    'flux-2-pro': '/api/ai/flux-2-pro',\n    'flux-kontext-pro': '/api/ai/flux-kontext-pro',","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/yollomi/utils.js#L80-L116","documentation":"DOWNLOAD_ERROR is thrown by downloadOutput when the fetch of the generated result URL returns a non-ok HTTP status. Result URLs on the host are typically signed/expiring, so a failed download usually means the link is no longer valid. The file is never written when this throws.","triggerScenarios":"downloadOutput(url, outputDir, filename) receives a URL whose fetch responds with 403/404/410 etc. — typically a result URL used after it expired, a truncated/mistyped URL, or the file removed from storage.","commonSituations":"Saving a result URL from a previous run and reusing it hours later, using --no-download output URLs in later scripts without re-downloading promptly, network proxies rejecting the storage host, or the provider purging old artifacts.","solutions":["Regenerate the result (rerun the command) to get a fresh URL and download immediately","Download promptly after generation — do not let signed URLs expire before fetching","Check the URL for truncation/copy errors if reusing a saved link","Test the URL in a browser/curl to confirm the HTTP status and whether it is a proxy issue"],"exampleFix":"// before\nconst { url } = previousRunResult;\nawait downloadOutput(url, out, 'img.png');  // 403: expired\n// after\nconst fresh = await upscale(page, { image }); // regenerate for a new URL\nawait downloadOutput(fresh.url, out, 'img.png'); // download right away","handlingStrategy":"fallback","validationCode":"// Pre-check the URL before committing to a download path\nconst head = await fetch(url, { method: 'HEAD' });\nif (!head.ok) throw new Error(`Result URL dead: HTTP ${head.status} — regenerate`);","typeGuard":"null","tryCatchPattern":"try {\n  await downloadOutput(url, outDir, name);\n} catch (e) {\n  if (e.code === 'DOWNLOAD_ERROR') {\n    console.error(`${e.message} — regenerating to get a fresh URL`);\n    const fresh = await regenerate();\n    return downloadOutput(fresh.url, outDir, name);\n  }\n  throw e;\n}","preventionTips":["Download immediately after generation; treat URLs as short-lived","Never persist result URLs for later reuse without re-validating","Retry the whole generation rather than retrying a dead URL"],"tags":["download","network","http-status","url-expiry"],"backgroundTag":"download-link-expired","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}