jackwener/OpenCLI · warning · CommandExecutionError

${label} returned HTTP 429 (rate limited)

Error message

${label} returned HTTP 429 (rate limited)

What it means

juejinFetch special-cases HTTP 429 from api.juejin.cn and raises a dedicated CommandExecutionError with rate-limit guidance. Juejin throttles bursty or high-frequency traffic on its public endpoints; the library does not auto-retry, it surfaces the condition so callers can back off.

Source

Thrown at clis/juejin/utils.js:105

    let resp;
    try {
        const init = {
            method,
            headers: { 'user-agent': UA, accept: 'application/json' },
        };
        if (method === 'POST') {
            init.headers['content-type'] = 'application/json';
            init.body = JSON.stringify(body ?? {});
        }
        resp = await fetch(url, init);
    } catch (err) {
        throw new CommandExecutionError(
            `${label} request failed: ${err?.message ?? err}`,
            'Check that api.juejin.cn is reachable from this network.',
        );
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'Juejin throttles bursty traffic; wait a few seconds and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let payload;
    try {
        payload = await resp.json();
    } catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'err_no')) {
        throw new CommandExecutionError(`${label} returned a malformed API envelope`);
    }
    if (payload.err_no !== 0) {
        throw new CommandExecutionError(`${label} returned err_no ${payload.err_no}: ${payload.err_msg ?? ''}`);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wait several seconds (or longer) and retry — Juejin's throttle window is typically short.
  2. Add exponential backoff with jitter around calls that hit 429 instead of failing the whole run.
  3. Reduce request frequency: batch pagination more aggressively (larger limits) and add delays between calls.
  4. Avoid running parallel workers against Juejin from the same IP.
  5. Check whether a shared network/VPN egress IP is causing other tenants' traffic to count against your limit.

Example fix

// before (tight loop, no delay)
for (const cursor of cursors) {
  await juejinFetch('/recommend_api/feed/v1', { cursor }, 'juejin recommend');
}

// after (sleep between pages)
const sleep = ms => new Promise(r => setTimeout(r, ms));
for (const cursor of cursors) {
  await juejinFetch('/recommend_api/feed/v1', { cursor }, 'juejin recommend');
  await sleep(1500);
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

function isRateLimited(err) {
  return err instanceof CommandExecutionError && /HTTP 429 \(rate limited\)/.test(err.message);
}

Try / catch

const sleep = ms => new Promise(r => setTimeout(r, ms));
async function withBackoff(fn, attempts = 4) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (err) {
      if (!isRateLimited(err) || i >= attempts - 1) throw err;
      await sleep(1000 * 2 ** i + Math.random() * 500);
    }
  }
}
const payload = await withBackoff(() => juejinFetch(path, body, label));

Prevention

When it happens

Trigger: The server responded with status 429 to a POST made by juejinFetch — i.e. the request succeeded at the transport level but Juejin's rate limiter rejected it. Happens when calling the adapter in a tight loop (pagination sweeps, scripts hitting recommend/hot endpoints repeatedly).

Common situations: A CI job or script iterating many pages without delay; multiple users sharing one NAT'd IP hitting Juejin; re-running a failed batch immediately instead of backing off; monitoring scripts polling every few seconds.

Understand the failure class

Related errors


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