jackwener/OpenCLI · error · CommandExecutionError

DuckDuckGo suggest returned malformed JSON: ${err?.message ?

Error message

DuckDuckGo suggest returned malformed JSON: ${err?.message ?? err}

What it means

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.

Source

Thrown at clis/duckduckgo/suggest.js:35

  columns: ['phrase'],
  func: async (kwargs) => {
    const limit = requireBoundedInteger(kwargs.limit, 8, 1, 20, '--limit');
    const keyword = encodeURIComponent(requireSearchQuery(kwargs.keyword));
    const url = `https://duckduckgo.com/ac/?q=${keyword}&type=list`;
    let resp;
    try {
      resp = await fetch(url);
    } catch (err) {
      throw new CommandExecutionError(`DuckDuckGo suggest request failed: ${err instanceof Error ? err.message : String(err)}`);
    }
    if (!resp.ok) {
      throw new CommandExecutionError(`DuckDuckGo suggest returned HTTP ${resp.status}`);
    }
    let data;
    try {
      data = await resp.json();
    } catch (err) {
      throw new CommandExecutionError(`DuckDuckGo suggest returned malformed JSON: ${err?.message ?? err}`);
    }
    const phrases = Array.isArray(data) && data.length > 1 && Array.isArray(data[1]) ? data[1] : [];
    return phrases
      .filter((phrase) => typeof phrase === 'string' && phrase.trim())
      .slice(0, limit)
      .map(function(p) { return { phrase: p }; });
  },
});

export const __test__ = { command };

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log the raw response text on failure to see what was actually returned
  2. Reduce request rate and retry; add jittered backoff
  3. Verify no proxy/firewall is rewriting responses
  4. Catch CommandExecutionError and treat as 'no suggestions' for that keyword
  5. Update parsing if DuckDuckGo changes the /ac/ response format

Example fix

// before
const data = JSON.parse(rawBody); // throws generic SyntaxError
// after
try {
  const data = await resp.json();
} catch (err) {
  const body = await resp.text().catch(() => '');
  console.error('unexpected body:', body.slice(0, 200));
  return [];
}
Defensive patterns

Strategy: fallback

Validate before calling

// sanity check content type before parsing expectations
const ct = resp.headers.get('content-type') ?? '';
if (!ct.includes('json')) {
  console.warn('suggest returned non-JSON content-type:', ct);
}

Type guard

function isSuggestPayload(d) {
  return Array.isArray(d) && d.length > 1 && Array.isArray(d[1]) && d[1].every(p => typeof p === 'string');
}

Try / catch

try {
  return await ddgSuggest({ keyword });
} catch (err) {
  if (/malformed JSON/.test(err?.message ?? '')) return []; // degrade gracefully
  throw err;
}

Prevention

When it happens

Trigger: resp.json() rejects: DuckDuckGo served an HTML error/anti-bot page with 200, empty body, or corrupted/truncated response; a proxy intercepted the response.

Common situations: Anti-bot interstitials returning 200 + HTML; captive portals in hotels/airports; aggressive request rates causing degraded responses; middlebox/proxy rewriting the body.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/85e308a26b51a765. Report an issue: GitHub.