jackwener/OpenCLI · error · CliError

DOWNLOAD_ERROR

DOWNLOAD_ERROR

Error message

DOWNLOAD_ERROR

What it means

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.

Source

Thrown at clis/yollomi/utils.js:98

    if (!fs.existsSync(resolved)) {
        throw new CliError('FILE_NOT_FOUND', `File not found: ${resolved}`, 'Provide a valid file path or URL');
    }
    const ext = path.extname(resolved).toLowerCase();
    const mimeMap = {
        '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
        '.png': 'image/png', '.gif': 'image/gif',
        '.webp': 'image/webp', '.bmp': 'image/bmp',
    };
    const mime = mimeMap[ext] || 'image/png';
    const data = fs.readFileSync(resolved);
    return `data:${mime};base64,${data.toString('base64')}`;
}
export async function downloadOutput(url, outputDir, filename) {
    fs.mkdirSync(outputDir, { recursive: true });
    const destPath = path.join(outputDir, filename);
    const resp = await fetch(url);
    if (!resp.ok)
        throw new CliError('DOWNLOAD_ERROR', `Download failed: HTTP ${resp.status}`, 'URL may have expired');
    const buffer = Buffer.from(await resp.arrayBuffer());
    fs.writeFileSync(destPath, buffer);
    return { path: destPath, size: buffer.length };
}
export function fmtBytes(bytes) {
    if (bytes === 0)
        return '0 B';
    const k = 1024;
    const sizes = ['B', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
}
/** Per-model API route mapping (matches frontend model.apiEndpoint). */
export const MODEL_ROUTES = {
    'flux': '/api/ai/flux',
    'flux-schnell': '/api/ai/flux-schnell',
    'flux-2-pro': '/api/ai/flux-2-pro',
    'flux-kontext-pro': '/api/ai/flux-kontext-pro',

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Regenerate the result (rerun the command) to get a fresh URL and download immediately
  2. Download promptly after generation — do not let signed URLs expire before fetching
  3. Check the URL for truncation/copy errors if reusing a saved link
  4. Test the URL in a browser/curl to confirm the HTTP status and whether it is a proxy issue

Example fix

// before
const { url } = previousRunResult;
await downloadOutput(url, out, 'img.png');  // 403: expired
// after
const fresh = await upscale(page, { image }); // regenerate for a new URL
await downloadOutput(fresh.url, out, 'img.png'); // download right away
Defensive patterns

Strategy: fallback

Validate before calling

// Pre-check the URL before committing to a download path
const head = await fetch(url, { method: 'HEAD' });
if (!head.ok) throw new Error(`Result URL dead: HTTP ${head.status} — regenerate`);

Type guard

null

Try / catch

try {
  await downloadOutput(url, outDir, name);
} catch (e) {
  if (e.code === 'DOWNLOAD_ERROR') {
    console.error(`${e.message} — regenerating to get a fresh URL`);
    const fresh = await regenerate();
    return downloadOutput(fresh.url, outDir, name);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/967643ab1ac689b4. Report an issue: GitHub.