jackwener/OpenCLI · error · CommandExecutionError
Failed to fetch WeRead search page: ${error instanceof Error
Error message
Failed to fetch WeRead search page: ${error instanceof Error ? error.message : String(error)} What it means
Thrown by loadSearchHtmlEntries in clis/weread/search.js when the Node.js fetch() to the WeRead server-rendered search page (https://weread.qq.com/web/search/books?keyword=...) rejects before a response is available. The library wraps the raw fetch failure in a CommandExecutionError and embeds the underlying error message (e.g. DNS failure, ECONNREFUSED, TLS error, timeout). This is a network-level failure, not an HTTP error status.
Source
Thrown at clis/weread/search.js:110
}
return titleOnlyQueues.get(titleKey)?.shift() ?? '';
}
/**
* Extract rendered search result reader URLs from the server-rendered search page.
* The public JSON API still returns bookId, but the current web app links results
* through /web/reader/<opaque-id> rather than /web/bookDetail/<bookId>.
*/
async function loadSearchHtmlEntries(query) {
const url = new URL('/web/search/books', WEREAD_WEB_ORIGIN);
url.searchParams.set('keyword', query);
let resp;
try {
resp = await fetch(url.toString(), {
headers: { 'User-Agent': WEREAD_UA },
});
}
catch (error) {
throw new CommandExecutionError(`Failed to fetch WeRead search page: ${error instanceof Error ? error.message : String(error)}`);
}
if (!resp.ok) {
throw new CommandExecutionError(`WeRead search page request failed: HTTP ${resp.status}`);
}
const html = await resp.text();
const items = Array.from(html.matchAll(/<li[^>]*class="wr_bookList_item"[^>]*>([\s\S]*?)<\/li>/g));
return items.map((match) => {
const chunk = match[1];
const hrefMatch = chunk.match(/<a[^>]*href="([^"]+)"[^>]*class="wr_bookList_item_link"[^>]*>|<a[^>]*class="wr_bookList_item_link"[^>]*href="([^"]+)"[^>]*>/);
const titleMatch = chunk.match(/<p[^>]*class="wr_bookList_item_title"[^>]*>([\s\S]*?)<\/p>/);
const authorMatch = chunk.match(/<p[^>]*class="wr_bookList_item_author"[^>]*>([\s\S]*?)<\/p>/);
const href = hrefMatch?.[1] || hrefMatch?.[2] || '';
const title = decodeHtmlText(titleMatch?.[1] || '');
const author = decodeHtmlText(authorMatch?.[1] || '');
return {
author,
url: href ? new URL(href, WEREAD_WEB_ORIGIN).toString() : '',
title,View on GitHub (pinned to 49907e53dc)
Solutions
- Check basic connectivity: curl -I https://weread.qq.com/web/search/books — if this fails, fix network/DNS/proxy first.
- If behind a corporate proxy, configure the environment (HTTPS_PROXY) and use a fetch implementation that honors it (e.g. undici ProxyAgent) since Node's global fetch does not by default.
- Retry the command — transient network blips and connection resets are the most common cause.
- Check DNS: nslookup weread.qq.com; add the host to /etc/hosts if local DNS is unreliable.
- If the failure is persistent and pages are intermittently unreachable, consider adding retry/backoff around loadSearchHtmlEntries.
Example fix
// before
resp = await fetch(url.toString(), { headers: { 'User-Agent': WEREAD_UA } });
// after
const agent = process.env.HTTPS_PROXY ? new ProxyAgent(process.env.HTTPS_PROXY) : undefined;
resp = await fetch(url.toString(), { headers: { 'User-Agent': WEREAD_UA }, ...(agent ? { dispatcher: agent } : {}) }); Defensive patterns
Strategy: retry
Validate before calling
// Preflight before calling `weread search`
const ok = await fetch('https://weread.qq.com/web/search/books?keyword=ping', { method: 'HEAD' })
.then(r => true).catch(() => false);
if (!ok) console.warn('weread.qq.com unreachable — check network/proxy/DNS before running search'); Try / catch
try {
const results = await runWereadSearch(query);
} catch (e) {
if (String(e.message).startsWith('Failed to fetch WeRead search page')) {
// network-level: retry once, then surface connectivity guidance
await sleep(2000);
retryOrNotifyUser(e);
} else throw e;
} Prevention
- Preflight connectivity to weread.qq.com in automation scripts before batch runs
- Configure HTTPS_PROXY-aware fetch when running behind corporate proxies
- Add exponential backoff retries around the search command
- Monitor DNS resolution for qq.com domains in CI environments
When it happens
Trigger: fetch() rejects for https://weread.qq.com/web/search/books — DNS resolution failure, no network connectivity, TLS handshake failure, connection refused/reset, or an aborted/timeout request. Raised from the search command which calls loadSearchHtmlEntries in Promise.all alongside fetchWebApi('/search/global').
Common situations: Offline machine or flaky Wi-Fi; corporate proxy or firewall blocking weread.qq.com; DNS misconfiguration (especially for .qq.com domains outside China); IPv6 issues; system clock skew breaking TLS; Node without proxy env vars honored in an environment that requires one.
Related errors
- `${label} request failed: ${err?.message ?? err}`
- ${label} request failed: ${err?.message ?? err}
- FETCH_ERROR
- Network failure fetching ${label}: ${detail}
- ${label} request failed: ${err?.message ?? err}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/b127e5855e0c037c.
Report an issue: GitHub.