jackwener/OpenCLI · error · TimeoutError

Suno generation did not complete within ${timeoutSeconds}s.

Error message

Suno generation did not complete within ${timeoutSeconds}s. Try --timeout <higher>.

What it means

pollSunoClips loops until every requested clip id reaches status 'complete' or 'error', or the timeout deadline passes. If the deadline expires first, TimeoutError is thrown, suggesting a higher --timeout.

Source

Thrown at clis/suno/utils.js:399

            throw new CommandExecutionError('Suno feed API returned malformed JSON while polling clips');
        }

        const allClips = result.body.clips || [];
        if (!Array.isArray(allClips)) {
            throw new CommandExecutionError('Suno feed API returned malformed clips payload');
        }
        const ourClips = allClips.filter(c => targetSet.has(c.id));
        const finished = ourClips.filter(c => c.status === 'complete' || c.status === 'error');

        if (typeof onProgress === 'function') {
            onProgress({ total: clipIds.length, done: finished.length, statuses: ourClips.map(c => `${c.id.slice(0,8)}:${c.status}`) });
        }

        if (finished.length === clipIds.length) return ourClips;
        await page.wait(pollSeconds);
    }

    throw new TimeoutError(`Suno generation did not complete within ${timeoutSeconds}s. Try --timeout <higher>.`);
}

// ─────────────────────────────────────────────────────────────────────────────
// Asset download.
// ─────────────────────────────────────────────────────────────────────────────

function pickMediaUrl(clip, contentTypeFragment) {
    const arr = Array.isArray(clip.media_urls) ? clip.media_urls : [];
    const hit = arr.find(m => (m.content_type || '').toLowerCase().includes(contentTypeFragment));
    return hit?.url || null;
}

/**
 * Resolve the canonical MP3 URL for a clip. Suno's /api/download/clip/{id}
 * returns a fresh signed URL; clip.audio_url is a backup.
 */
async function resolveMp3Url(page, clip, deviceId) {
    const fromApi = await page.evaluate(`(async () => {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run with a higher --timeout (e.g. --timeout 600).
  2. Check suno.com library page — the clip may have completed after the CLI gave up.
  3. Increase pollSeconds slightly and retry during off-peak hours if the queue is congested.
  4. Verify the clip ids are valid; ids absent from the feed will never satisfy the completion check.

Example fix

// before
await pollSunoClips(page, ids, 120, deviceId);
// after
await pollSunoClips(page, ids, 600, deviceId); // or CLI: --timeout 600
Defensive patterns

Strategy: fallback

Try / catch

try {
  const clips = await pollSunoClips(page, ids, 300, deviceId);
} catch (e) {
  if (e instanceof TimeoutError) {
    // fall back to checking the clips later or with a longer timeout
    console.warn('Generation still pending; retry with --timeout 600 or check suno.com library');
  } else throw e;
}

Prevention

When it happens

Trigger: Within timeoutSeconds, at least one requested clip never reports complete/error via /api/feed/v3 — generation still queued/processing, a clip stuck in a nonterminal status, or the clip id never appearing in the feed.

Common situations: Long songs or high queue load during peak hours; very low --timeout (default too short for batch requests); Suno outage leaving generations pending indefinitely; invalid clip ids that never show in feed.

Understand the failure class

Related errors


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