jackwener/OpenCLI · error · CommandExecutionError

DuckDuckGo suggest returned HTTP ${resp.status}

Error message

DuckDuckGo suggest returned HTTP ${resp.status}

What it means

After the fetch resolves, the suggest command checks resp.ok. Any non-2xx HTTP status from the DuckDuckGo autocomplete endpoint (429 rate limit, 403 blocked, 5xx server error) produces this CommandExecutionError with the status code embedded. Unlike error 1317, the request reached the server but was rejected.

Source

Thrown at clis/duckduckgo/suggest.js:29

  strategy: Strategy.PUBLIC,
  browser: false,
  args: [
    { name: 'keyword', positional: true, required: true, help: 'Search query prefix' },
    { name: 'limit', type: 'int', default: 8, help: 'Max number of suggestions' },
  ],
  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. Read the embedded HTTP status in the message to identify the cause
  2. Add delays/backoff between suggest requests; back off aggressively on 429
  3. Retry later on 5xx; check DuckDuckGo status if persistent
  4. Use a different network/IP if 403-blocked (avoid datacenter IPs)
  5. Catch CommandExecutionError and degrade gracefully (return empty suggestions)

Example fix

// before
for (const k of keywords) suggestions[k] = await ddgSuggest({ keyword: k });
// after
for (const k of keywords) {
  await sleep(1000); // avoid 429
  try { suggestions[k] = await ddgSuggest({ keyword: k }); }
  catch (e) { suggestions[k] = []; }
}
Defensive patterns

Strategy: retry

Validate before calling

// throttle yourself: at most 1 suggest call per second
let last = 0;
async function throttledSuggest(kw) {
  const wait = Math.max(0, 1000 - (Date.now() - last));
  await sleep(wait); last = Date.now();
  return ddgSuggest({ keyword: kw });
}

Try / catch

try {
  return await ddgSuggest({ keyword });
} catch (err) {
  const m = /HTTP (\d+)/.exec(err?.message ?? '');
  const status = m ? Number(m[1]) : 0;
  if (status === 429 || status >= 500) {
    await sleep(status === 429 ? 10000 : 2000);
    return ddgSuggest({ keyword });
  }
  if (status === 403 || status === 404) return []; // blocked/not offered
  throw err;
}

Prevention

When it happens

Trigger: DuckDuckGo returns 403 (bot/anti-abuse block), 429 (rate limited from rapid repeated calls), 5xx (server-side incident), or a captive portal's 30x/200 HTML handled as non-ok.

Common situations: Looping over many keywords too quickly and hitting rate limits; running from datacenter/CI IPs that DuckDuckGo throttles; DuckDuckGo outage; blocked region or VPN IP.

Related errors


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