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
- Add exponential backoff with jitter and/or increase the attempt budget for 429-heavy routes.
- Serialize or debounce announcement jobs so concurrent CI runs don't hammer the same webhook; cache the discussion lookup between runs.
- Reduce call frequency: reuse the existing announcement discussion instead of re-querying on every invocation.
- Inspect the 429 response body's retry_after and respect it fully instead of capping at 10s.
- 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
- Honor Discord's ~5 messages / 2s per-webhook limit and GitHub secondary rate limits.
- Add jittered exponential backoff rather than a fixed sleep cap.
- Debounce concurrent CI jobs so only one announcement runs per release.
- Reuse the existing announcement discussion instead of re-querying on every run.
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
- gh pr create failed
- GitHub GraphQL request failed
- GitHub receipt configuration is missing
- announcement delivered; pending receipt cleanup requires…
- announcement has a pending receipt; inspect Discord before…
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)