affaan-m/ECC · error · Error

request retry budget exhausted

Error message

request retry budget exhausted

What it means

The request() helper retries fetch calls up to a fixed attempt budget when the API responds with HTTP 429, sleeping per the server's retry_after hint (capped at 10s). If every attempt returns 429 it throws 'request retry budget exhausted', indicating sustained rate limiting rather than a transient blip.

Solutions

  1. Add exponential backoff with jitter and/or increase the attempt budget for 429-heavy routes.
  2. Serialize or debounce announcement jobs so concurrent CI runs don't hammer the same webhook; cache the discussion lookup between runs.
  3. Reduce call frequency: reuse the existing announcement discussion instead of re-querying on every invocation.
  4. Inspect the 429 response body's retry_after and respect it fully instead of capping at 10s.
  5. Spread webhook posts out — Discord enforces roughly 5 messages per 2 seconds per webhook.

Example fix

// before
const response = await request(url, options);
// after
await new Promise(r => setTimeout(r, 1000 + Math.random() * 2000)); // pre-request spacing
const response = await request(url, options);
if (response.status === 429) {
  const hint = (await response.json().catch(() => ({}))).retry_after ?? 5;
  await new Promise(r => setTimeout(r, Math.min(hint * 1000, 30_000)));
}
Defensive patterns

Strategy: retry

Try / catch

try {
  const res = await request(url, options);
} catch (err) {
  if (err.message === 'request retry budget exhausted') {
    console.error('Still rate-limited after all retries; backing off before the next workflow run.');
    process.exitCode = 1; // let CI retry later rather than hammering
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Calling Discord webhook or GitHub endpoints faster than the per-route rate limit allows; many parallel workflow jobs announcing the same release; a webhook already near its 5/2s or 30/min limit; retry_after hints exceeding the 10s sleep cap so the backoff is insufficient.

Common situations: CI matrices fanning out announcements for the same tag; a looping bot script re-running every few seconds; a shared webhook used by multiple pipelines; GitHub secondary rate limits triggered by bursty API usage.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/09f3ca2ea68e20da. Report an issue: GitHub.

Appendix: source

Thrown at scripts/discord/release-announce.mjs:26

  discussionReceiptStatus,
  findDiscussionReceipt,
  findDiscordReceipt,
  findReleaseDiscussion,
  normalizeDiscordWebhookUrl,
  releaseMarker,
} from './announcement-core.mjs';

const env = process.env;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

async function request(url, options = {}, attempts = 3) {
  for (let attempt = 1; attempt <= attempts; attempt += 1) {
    const response = await fetch(url, options);
    if (response.status !== 429 || attempt === attempts) return response;
    const data = await response.json().catch(() => ({}));
    await sleep(Math.min(Number(data.retry_after || 1) * 1000 + 250, 10_000));
  }
  throw new Error('request retry budget exhausted');
}

async function githubGraphql(query, variables) {
  const response = await request('https://api.github.com/graphql', {
    method: 'POST',
    headers: { Authorization: `Bearer ${env.GITHUB_TOKEN}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query, variables }),
  });
  if (!response.ok) throw new Error(`GitHub GraphQL request failed (${response.status})`);
  const payload = await response.json();
  if (payload.errors) throw new Error('GitHub GraphQL returned errors');
  return payload.data;
}

async function releaseFromGitHub() {
  const [owner, repo] = env.GITHUB_REPOSITORY.split('/');
  const tag = env.RELEASE_TAG || env.GITHUB_REF_NAME;
  const response = await request(`https://api.github.com/repos/${owner}/${repo}/releases/tags/${encodeURIComponent(tag)}`, {

View on GitHub (pinned to 8321021c54)