jackwener/OpenCLI · warning · CommandExecutionError

${label} returned HTTP 429 (rate limited)

Error message

${label} returned HTTP 429 (rate limited)

What it means

npmFetch treats HTTP 429 as a distinct rate-limiting condition. npm's public registry throttles unauthenticated clients that make too many requests in a short burst, so when a 429 status is returned the helper throws CommandExecutionError with remediation guidance. This lets callers know the failure is transient and retrying after a delay should succeed.

Source

Thrown at clis/npm/utils.js:60

    return n;
}

export async function npmFetch(url, label) {
    let resp;
    try {
        resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
    }
    catch (err) {
        throw new CommandExecutionError(
            `${label} request failed: ${err?.message ?? err}`,
            'Check that registry.npmjs.org / api.npmjs.org are reachable from this network.',
        );
    }
    if (resp.status === 404) {
        throw new EmptyResultError(label, `npm registry returned 404 for ${url}.`);
    }
    if (resp.status === 429) {
        throw new CommandExecutionError(
            `${label} returned HTTP 429 (rate limited)`,
            'npm throttles unauthenticated bursts; wait a few seconds and retry.',
        );
    }
    if (!resp.ok) {
        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
    }
    let body;
    try {
        body = await resp.json();
    }
    catch (err) {
        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
    }
    return body;
}

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wait a few seconds and retry — npm throttles unauthenticated bursts, so a short backoff usually clears it
  2. Add exponential backoff with jitter around npmFetch calls (e.g. retry after 1s, 2s, 4s)
  3. Reduce request volume: batch/limit the number of packages queried per run, or add a delay between requests
  4. Use an authenticated npm token where the API supports it, to raise the rate ceiling

Example fix

// before
const data = await npmFetch(url, 'npm package');
// after
async function fetchWithBackoff(url, label, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await npmFetch(url, label);
    } catch (err) {
      if (!String(err.message).includes('429') || i === retries - 1) throw err;
      await new Promise((r) => setTimeout(r, 1000 * 2 ** i));
    }
  }
}
Defensive patterns

Strategy: retry

Validate before calling

null

Type guard

null

Try / catch

try {
  return await npmFetch(url, label);
} catch (err) {
  if (/HTTP 429/.test(String(err.message))) {
    await sleep(2000); // or retry with exponential backoff
    return await npmFetch(url, label);
  }
  throw err;
}

Prevention

When it happens

Trigger: A burst of npmFetch calls to registry.npmjs.org or api.npmjs.org without authentication exceeding npm's per-IP/per-client rate budget — e.g. looping over dozens of package names, running the CLI in CI across many parallel jobs, or shared CI runner IPs already exhausted by other tenants.

Common situations: CI pipelines (GitHub Actions, shared runners) hitting npm from well-known throttled IP ranges; scripts iterating over a large package list without delay or backoff; multiple developer tools on the same network all polling the registry simultaneously.

Understand the failure class

Related errors


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