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
- Increase the timeout: --wait 30 (or higher) to give the idle detector more budget.
- Abandon networkidle for this page and use --wait-for <selector> on a stable element.
- Check devtools network tab for endless polling/streaming endpoints; if present, networkidle is unattainable by design.
- Block noisy trackers/scripts (via proxy/adblock or the tool's blocking options) so activity can settle.
- 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
- Never use networkidle on pages with long-polling, SSE, or WebSockets
- Give networkidle a generous --wait budget (20s+)
- Prefer --wait-for element readiness for deterministic pages
- Block trackers/ads in the automation profile to let the network settle
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Douyin search extraction failed: ${error instanceof Error ?
- TimeoutError
- IMDb search results did not finish loading
- Timed out waiting for selector "${kwargs['wait-for']}" in ma
- weixin search failed while loading Sogou results
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/b0bc3cfcd42ad96a.
Report an issue: GitHub.