jackwener/OpenCLI · error · Error

Timed out waiting for network idle after ${waitSeconds}s

Error message

Timed out waiting for network idle after ${waitSeconds}s

What it means

With --wait-until networkidle, the command waits (waitForNetworkIdle) for the page's network activity to settle within the --wait window. If requests keep firing (or never finish) when the budget expires, waitForNetworkIdle returns not-ok and this timeout error is thrown.

Source

Thrown at clis/web/read.js:443

        await page.goto(url);
        if (kwargs['wait-for']) {
            const waitResult = await page.evaluate(buildWaitForSelectorAcrossFramesJs(String(kwargs['wait-for']), waitSeconds * 1000));
            if (waitResult?.invalidSelector) {
                throw new Error(`Invalid --wait-for selector "${kwargs['wait-for']}": ${waitResult.error || 'querySelector failed'}`);
            }
            if (!waitResult?.ok) {
                throw new Error(`Timed out waiting for selector "${kwargs['wait-for']}" in main document or same-origin iframes`);
            }
        } else if (waitUntil !== 'networkidle') {
            await page.wait(waitSeconds);
        }
        if (waitUntil === 'networkidle') {
            if (!captureSupported) {
                throw new Error('Network capture is unavailable, so --wait-until networkidle cannot be satisfied');
            }
            const idle = await waitForNetworkIdle(page, waitSeconds, networkEntries);
            if (!idle?.ok) {
                throw new Error(`Timed out waiting for network idle after ${waitSeconds}s`);
            }
        }
        // Extract article content using browser-side heuristics
        const data = await page.evaluate(buildRenderAwareExtractorJs({ frames: frameMode }));
        if (captureSupported) await drainNetworkCapture(page, networkEntries);
        if (shouldDiagnose) process.stderr.write(formatDiagnostics(data, networkEntries, captureSupported));
        // Determine Referer from URL for image downloads
        let referer = '';
        try {
            const parsed = new URL(url);
            referer = parsed.origin + '/';
        }
        catch { /* ignore */ }
        const result = await downloadArticle({
            title: data?.title || 'untitled',
            author: data?.author,
            publishTime: data?.publishTime,
            sourceUrl: url,

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Increase the timeout: --wait 30 (or higher) to give the idle detector more budget.
  2. Abandon networkidle for this page and use --wait-for <selector> on a stable element.
  3. Check devtools network tab for endless polling/streaming endpoints; if present, networkidle is unattainable by design.
  4. Block noisy trackers/scripts (via proxy/adblock or the tool's blocking options) so activity can settle.
  5. Verify the page isn't stuck retrying failed requests due to auth or network issues.

Example fix

// before
web read https://dashboard.example.com --wait-until networkidle --wait 3
// after
web read https://dashboard.example.com --wait-until networkidle --wait 30
// or, if the page polls forever:
// web read https://dashboard.example.com --wait-for '#dashboard-ready'
Defensive patterns

Strategy: retry

Validate before calling

// Detect endless-polling pages before choosing networkidle
const hasPolling = await page.evaluate(`performance.getEntriesByType('resource')
  .filter(r => r.initiatorType === 'xmlhttprequest' || r.initiatorType === 'fetch').length > 20`);
if (hasPolling) useWaitForSelectorInstead = true;

Type guard

function isIdleResult(r) { return r != null && typeof r === 'object' && typeof r.ok === 'boolean'; }

Try / catch

try {
  await readPage(url, { waitUntil: 'networkidle', waitSeconds: 30 });
} catch (e) {
  if (/Timed out waiting for network idle/.test(e.message)) {
    await readPage(url, { waitFor: '#content', waitSeconds: 30 }); // deterministic fallback
  } else throw e;
}

Prevention

When it happens

Trigger: Pages with long-polling/WebSocket/streaming requests, analytics beacons that keep firing, slow or hanging third-party scripts, or a very small --wait value — any case where in-flight/ongoing requests persist past waitSeconds so the idle check returns ok:false.

Common situations: Scraping dashboards with auto-refresh polling; pages with chat/telemetry sockets; ad-heavy pages with slow trackers; running on slow networks with the default short timeout.

Understand the failure class

Related errors


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