{"record":{"id":"b127e5855e0c037c","repo":"jackwener/OpenCLI","slug":"failed-to-fetch-weread-search-page-error-instan","errorCode":null,"errorMessage":"Failed to fetch WeRead search page: ${error instanceof Error ? error.message : String(error)}","messagePattern":"Failed to fetch WeRead search page: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/weread/search.js","lineNumber":110,"sourceCode":"    }\n    return titleOnlyQueues.get(titleKey)?.shift() ?? '';\n}\n/**\n * Extract rendered search result reader URLs from the server-rendered search page.\n * The public JSON API still returns bookId, but the current web app links results\n * through /web/reader/<opaque-id> rather than /web/bookDetail/<bookId>.\n */\nasync function loadSearchHtmlEntries(query) {\n    const url = new URL('/web/search/books', WEREAD_WEB_ORIGIN);\n    url.searchParams.set('keyword', query);\n    let resp;\n    try {\n        resp = await fetch(url.toString(), {\n            headers: { 'User-Agent': WEREAD_UA },\n        });\n    }\n    catch (error) {\n        throw new CommandExecutionError(`Failed to fetch WeRead search page: ${error instanceof Error ? error.message : String(error)}`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`WeRead search page request failed: HTTP ${resp.status}`);\n    }\n    const html = await resp.text();\n    const items = Array.from(html.matchAll(/<li[^>]*class=\"wr_bookList_item\"[^>]*>([\\s\\S]*?)<\\/li>/g));\n    return items.map((match) => {\n        const chunk = match[1];\n        const hrefMatch = chunk.match(/<a[^>]*href=\"([^\"]+)\"[^>]*class=\"wr_bookList_item_link\"[^>]*>|<a[^>]*class=\"wr_bookList_item_link\"[^>]*href=\"([^\"]+)\"[^>]*>/);\n        const titleMatch = chunk.match(/<p[^>]*class=\"wr_bookList_item_title\"[^>]*>([\\s\\S]*?)<\\/p>/);\n        const authorMatch = chunk.match(/<p[^>]*class=\"wr_bookList_item_author\"[^>]*>([\\s\\S]*?)<\\/p>/);\n        const href = hrefMatch?.[1] || hrefMatch?.[2] || '';\n        const title = decodeHtmlText(titleMatch?.[1] || '');\n        const author = decodeHtmlText(authorMatch?.[1] || '');\n        return {\n            author,\n            url: href ? new URL(href, WEREAD_WEB_ORIGIN).toString() : '',\n            title,","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/search.js#L92-L128","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","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."],"exampleFix":"// before\nresp = await fetch(url.toString(), { headers: { 'User-Agent': WEREAD_UA } });\n// after\nconst agent = process.env.HTTPS_PROXY ? new ProxyAgent(process.env.HTTPS_PROXY) : undefined;\nresp = await fetch(url.toString(), { headers: { 'User-Agent': WEREAD_UA }, ...(agent ? { dispatcher: agent } : {}) });","handlingStrategy":"retry","validationCode":"// Preflight before calling `weread search`\nconst ok = await fetch('https://weread.qq.com/web/search/books?keyword=ping', { method: 'HEAD' })\n  .then(r => true).catch(() => false);\nif (!ok) console.warn('weread.qq.com unreachable — check network/proxy/DNS before running search');","typeGuard":null,"tryCatchPattern":"try {\n  const results = await runWereadSearch(query);\n} catch (e) {\n  if (String(e.message).startsWith('Failed to fetch WeRead search page')) {\n    // network-level: retry once, then surface connectivity guidance\n    await sleep(2000);\n    retryOrNotifyUser(e);\n  } else throw e;\n}","preventionTips":["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"],"tags":["network","fetch","weread","dns","connectivity"],"backgroundTag":"fetch-network-failure","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}