{"record":{"id":"c4b50014e6e2ad69","repo":"jackwener/OpenCLI","slug":"label-returned-invalid-json-c4b500","errorCode":null,"errorMessage":"${label} returned invalid JSON","messagePattern":"(.+?) returned invalid JSON","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/weread/book-search.js","lineNumber":94,"sourceCode":"\nasync function fetchJson(url, label) {\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(`${label} request failed: ${error instanceof Error ? error.message : String(error)}`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} request failed: HTTP ${resp.status}`);\n    }\n    try {\n        return await resp.json();\n    }\n    catch {\n        throw new CommandExecutionError(`${label} returned invalid JSON`);\n    }\n}\n\nasync function fetchText(url, label) {\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(`${label} request failed: ${error instanceof Error ? error.message : String(error)}`);\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} request failed: HTTP ${resp.status}`);\n    }\n    return resp.text();\n}","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/book-search.js#L76-L112","documentation":"fetchJson wraps resp.json() in try/catch and throws CommandExecutionError '<label> returned invalid JSON' when the response body cannot be parsed. The server returned 2xx but the body is not valid JSON (HTML error page, empty body, truncated response, or an anti-bot interstitial).","triggerScenarios":"WeRead returns an HTML login/captcha page with status 200, an empty body, a proxy injects its own HTML error page, or the response is truncated mid-transfer causing JSON.parse to fail.","commonSituations":"Captive portals or corporate proxies rewriting 200 responses, session/login requirements added server-side, CDN returning cached HTML for an API path, or intermittent truncation on flaky connections.","solutions":["Inspect the raw response with curl (same URL and User-Agent) to see what the body actually is","Retry — truncated/intermittent responses may succeed on retry","Check for proxy/captive-portal interference and bypass it","If WeRead now requires auth or changed the response format, update the CLI","Log the first bytes of the body before parsing in a patched build to diagnose"],"exampleFix":"// before\nlet data;\ntry { data = await fetchJson(url, 'WeRead book search'); } catch (e) { throw e; }\n// after (fetch raw text to diagnose non-JSON bodies)\nconst resp = await fetch(url, { headers: { 'User-Agent': WEREAD_UA } });\nconst text = await resp.text();\nif (!text.trim().startsWith('{') && !text.trim().startsWith('[')) {\n  throw new Error('Non-JSON body (first 200 chars): ' + text.slice(0, 200));\n}\nconst data = JSON.parse(text);","handlingStrategy":"fallback","validationCode":"// Inspect the raw body shape before trusting json parsing\nconst resp = await fetch(url, { headers: { 'User-Agent': WEREAD_UA } });\nconst text = await resp.text();\nconst looksJson = /^[\\s]*[[{]/.test(text);\nif (!looksJson) throw new Error('Non-JSON response: ' + text.slice(0, 120));","typeGuard":"const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":"try {\n  const data = await fetchJson(url, 'WeRead book search');\n} catch (e) {\n  if (e instanceof CommandExecutionError && e.message.includes('returned invalid JSON')) {\n    // fallback: log body, or fall back to HTML scraping path\n    console.error('WeRead returned a non-JSON body (login page/proxy?).');\n  } else throw e;\n}","preventionTips":["Check for captive portals/proxies that rewrite 200 responses into HTML","Log or capture the raw body on failure to distinguish captcha pages from truncation","Retry once on parse failure — truncated bodies are often transient","Keep the CLI updated for auth/response-format changes on WeRead's side"],"tags":["network","json-parsing","api-change"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}