{"record":{"id":"85e308a26b51a765","repo":"jackwener/OpenCLI","slug":"duckduckgo-suggest-returned-malformed-json-err","errorCode":null,"errorMessage":"DuckDuckGo suggest returned malformed JSON: ${err?.message ?? err}","messagePattern":"DuckDuckGo suggest returned malformed JSON: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/duckduckgo/suggest.js","lineNumber":35,"sourceCode":"  columns: ['phrase'],\n  func: async (kwargs) => {\n    const limit = requireBoundedInteger(kwargs.limit, 8, 1, 20, '--limit');\n    const keyword = encodeURIComponent(requireSearchQuery(kwargs.keyword));\n    const url = `https://duckduckgo.com/ac/?q=${keyword}&type=list`;\n    let resp;\n    try {\n      resp = await fetch(url);\n    } catch (err) {\n      throw new CommandExecutionError(`DuckDuckGo suggest request failed: ${err instanceof Error ? err.message : String(err)}`);\n    }\n    if (!resp.ok) {\n      throw new CommandExecutionError(`DuckDuckGo suggest returned HTTP ${resp.status}`);\n    }\n    let data;\n    try {\n      data = await resp.json();\n    } catch (err) {\n      throw new CommandExecutionError(`DuckDuckGo suggest returned malformed JSON: ${err?.message ?? err}`);\n    }\n    const phrases = Array.isArray(data) && data.length > 1 && Array.isArray(data[1]) ? data[1] : [];\n    return phrases\n      .filter((phrase) => typeof phrase === 'string' && phrase.trim())\n      .slice(0, limit)\n      .map(function(p) { return { phrase: p }; });\n  },\n});\n\nexport const __test__ = { command };\n","sourceCodeStart":17,"sourceCodeEnd":46,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/duckduckgo/suggest.js#L17-L46","documentation":"When the suggest endpoint responds 2xx but resp.json() throws, the command wraps the parse failure in a CommandExecutionError with the message 'DuckDuckGo suggest returned malformed JSON'. This happens when the body is not the expected JSON array-of-arrays shape — typically HTML (block page), an empty body, or truncated output.","triggerScenarios":"resp.json() rejects: DuckDuckGo served an HTML error/anti-bot page with 200, empty body, or corrupted/truncated response; a proxy intercepted the response.","commonSituations":"Anti-bot interstitials returning 200 + HTML; captive portals in hotels/airports; aggressive request rates causing degraded responses; middlebox/proxy rewriting the body.","solutions":["Log the raw response text on failure to see what was actually returned","Reduce request rate and retry; add jittered backoff","Verify no proxy/firewall is rewriting responses","Catch CommandExecutionError and treat as 'no suggestions' for that keyword","Update parsing if DuckDuckGo changes the /ac/ response format"],"exampleFix":"// before\nconst data = JSON.parse(rawBody); // throws generic SyntaxError\n// after\ntry {\n  const data = await resp.json();\n} catch (err) {\n  const body = await resp.text().catch(() => '');\n  console.error('unexpected body:', body.slice(0, 200));\n  return [];\n}","handlingStrategy":"fallback","validationCode":"// sanity check content type before parsing expectations\nconst ct = resp.headers.get('content-type') ?? '';\nif (!ct.includes('json')) {\n  console.warn('suggest returned non-JSON content-type:', ct);\n}","typeGuard":"function isSuggestPayload(d) {\n  return Array.isArray(d) && d.length > 1 && Array.isArray(d[1]) && d[1].every(p => typeof p === 'string');\n}","tryCatchPattern":"try {\n  return await ddgSuggest({ keyword });\n} catch (err) {\n  if (/malformed JSON/.test(err?.message ?? '')) return []; // degrade gracefully\n  throw err;\n}","preventionTips":["Reduce request rate — degraded/HTML responses often follow abuse detection","Verify content-type before parsing when using raw fetch","Return [] as a fallback so one bad response does not break batch jobs","Capture the raw body on parse failure for diagnostics"],"tags":["json-parse","malformed-response","cli"],"backgroundTag":"invalid-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}