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
- Wait several seconds (or longer) and retry — Juejin's throttle window is typically short.
- Add exponential backoff with jitter around calls that hit 429 instead of failing the whole run.
- Reduce request frequency: batch pagination more aggressively (larger limits) and add delays between calls.
- Avoid running parallel workers against Juejin from the same IP.
- 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
- Add sleep/delay between paginated calls; never loop juejinFetch back-to-back.
- Implement exponential backoff with jitter for any adapter-driven retry loop.
- Serialize Juejin calls from one process — avoid parallel workers sharing the same IP budget.
- Prefer larger page limits over many small pages to cut total request count.
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
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- coingecko derivatives returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- coingecko returned HTTP 429 (rate limited)
- ${label} returned HTTP 429 (rate limited)
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/fe84b7c38b802181.
Report an issue: GitHub.