Skip to content

Commit 5ae130e

Browse files
committed
chore: log AI rate-limit headers and 429 count in backfill
Capture Retry-After, X-RateLimit-* and request id on 429 responses, log them on retry and final skip, and include total AI 429 hits in the workflow summary table.
1 parent 6abbce9 commit 5ae130e

1 file changed

Lines changed: 51 additions & 3 deletions

File tree

.github/workflows/labeler.yml

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,7 @@ jobs:
948948
const failedIssues = [];
949949
let aiGameAttempts = 0;
950950
let aiGameMatches = 0;
951+
let aiGameRateLimited = 0;
951952
952953
// === Helpers (mirrored from issue-ai-maintenance) ===
953954
@@ -1027,6 +1028,39 @@ jobs:
10271028
].some((term) => normalized.includes(term));
10281029
}
10291030
1031+
function parseAiRateLimitInfo(response) {
1032+
const retryAfter = response.headers.get('retry-after') || response.headers.get('Retry-After') || '';
1033+
const limit = response.headers.get('x-ratelimit-limit') || '';
1034+
const remaining = response.headers.get('x-ratelimit-remaining') || '';
1035+
const resetEpoch = response.headers.get('x-ratelimit-reset') || '';
1036+
const requestId = response.headers.get('x-github-request-id') || '';
1037+
1038+
let resetIso = '';
1039+
const parsedReset = Number.parseInt(resetEpoch, 10);
1040+
if (Number.isFinite(parsedReset) && parsedReset > 0) {
1041+
resetIso = new Date(parsedReset * 1000).toISOString();
1042+
}
1043+
1044+
return {
1045+
retryAfter,
1046+
limit,
1047+
remaining,
1048+
resetEpoch,
1049+
resetIso,
1050+
requestId,
1051+
};
1052+
}
1053+
1054+
function formatAiRateLimitInfo(info) {
1055+
const parts = [];
1056+
if (info.retryAfter) parts.push(`retry-after=${info.retryAfter}s`);
1057+
if (info.limit) parts.push(`limit=${info.limit}`);
1058+
if (info.remaining) parts.push(`remaining=${info.remaining}`);
1059+
if (info.resetEpoch) parts.push(`reset=${info.resetEpoch}${info.resetIso ? ` (${info.resetIso})` : ''}`);
1060+
if (info.requestId) parts.push(`request-id=${info.requestId}`);
1061+
return parts.length > 0 ? parts.join(', ') : 'no rate-limit headers returned';
1062+
}
1063+
10301064
function hasAliasHitForLabel(text, targetLabel, gameAliasToLabel) {
10311065
const normalizedText = normalizeName(text);
10321066
if (!normalizedText || !targetLabel) return false;
@@ -1406,8 +1440,13 @@ jobs:
14061440
14071441
// On 429 honour Retry-After (capped at 60 s) then retry once.
14081442
if (res.status === 429) {
1409-
const retryAfter = Math.min(Number.parseInt(res.headers.get('Retry-After') || '10', 10), 60);
1410-
console.log(`#${rawIssue.number}: AI fallback rate-limited — waiting ${retryAfter}s then retrying…`);
1443+
aiGameRateLimited += 1;
1444+
const rateInfo = parseAiRateLimitInfo(res);
1445+
const rawRetryAfter = Number.parseInt(rateInfo.retryAfter || '10', 10);
1446+
const retryAfter = Math.min(Number.isFinite(rawRetryAfter) ? rawRetryAfter : 10, 60);
1447+
console.log(
1448+
`#${rawIssue.number}: AI fallback rate-limited - waiting ${retryAfter}s then retrying (${formatAiRateLimitInfo(rateInfo)})`
1449+
);
14111450
await new Promise((r) => setTimeout(r, retryAfter * 1000));
14121451
res = await fetch(aiUrl, { method: 'POST', headers: aiHeaders, body: JSON.stringify(aiPayload) });
14131452
}
@@ -1454,7 +1493,14 @@ jobs:
14541493
}
14551494
}
14561495
} else {
1457-
console.log(`#${rawIssue.number}: AI fallback skipped (HTTP ${res.status})`);
1496+
if (res.status === 429) {
1497+
const rateInfo = parseAiRateLimitInfo(res);
1498+
console.log(
1499+
`#${rawIssue.number}: AI fallback skipped (HTTP 429, ${formatAiRateLimitInfo(rateInfo)})`
1500+
);
1501+
} else {
1502+
console.log(`#${rawIssue.number}: AI fallback skipped (HTTP ${res.status})`);
1503+
}
14581504
}
14591505
} catch (err) {
14601506
console.log(`#${rawIssue.number}: AI fallback error: ${err.message}`);
@@ -1565,6 +1611,7 @@ jobs:
15651611
{ data: 'AI fallback', header: true },
15661612
{ data: 'AI attempts', header: true },
15671613
{ data: 'AI matches', header: true },
1614+
{ data: 'AI 429s', header: true },
15681615
{ data: 'Target issues', header: true },
15691616
{ data: 'Processed', header: true },
15701617
{ data: 'Failures', header: true },
@@ -1575,6 +1622,7 @@ jobs:
15751622
useAiGameFallback ? 'enabled' : 'disabled',
15761623
String(aiGameAttempts),
15771624
String(aiGameMatches),
1625+
String(aiGameRateLimited),
15781626
String(selectedTargets.length),
15791627
String(processed),
15801628
String(failedIssues.length),

0 commit comments

Comments
 (0)