Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 38 additions & 1 deletion .github/workflows/bot-detection.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,31 @@ jobs:
fs.appendFileSync(summaryPath, `${markdown}\n`);
}

function buildAccountSummaryTable(sortedAccounts, limit) {
const rows = [];
const capped = sortedAccounts.slice(0, limit);
for (const [login, data] of capped) {
const openIssues = (data.issues || []).filter(i => i.state === 'open').length;
const closedIssues = (data.issues || []).filter(i => i.state === 'closed').length;
const openPRs = (data.prs || []).filter(p => p.state === 'open').length;
const closedPRs = (data.prs || []).filter(p => p.state === 'closed').length;
Comment on lines +38 to +41
Copy link

Copilot AI Feb 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildAccountSummaryTable scans data.issues and data.prs twice each (open + closed) via separate filter(...) calls. This is unnecessary work and can become noticeably slower if an account has many items; consider counting states in a single pass (e.g., one loop/reduce) per collection.

Suggested change
const openIssues = (data.issues || []).filter(i => i.state === 'open').length;
const closedIssues = (data.issues || []).filter(i => i.state === 'closed').length;
const openPRs = (data.prs || []).filter(p => p.state === 'open').length;
const closedPRs = (data.prs || []).filter(p => p.state === 'closed').length;
const issues = data.issues || [];
let openIssues = 0;
let closedIssues = 0;
for (const i of issues) {
if (i.state === 'open') {
openIssues++;
} else if (i.state === 'closed') {
closedIssues++;
}
}
const prs = data.prs || [];
let openPRs = 0;
let closedPRs = 0;
for (const p of prs) {
if (p.state === 'open') {
openPRs++;
} else if (p.state === 'closed') {
closedPRs++;
}
}

Copilot uses AI. Check for mistakes.
const comments = (data.comments || []).length;

rows.push(`| @${login} | ${data.daysOld}d | ${openIssues} | ${closedIssues} | ${openPRs} | ${closedPRs} | ${comments} |`);
}

const header = [
'| Account | Age | Issues (open) | Issues (closed) | PRs (open) | PRs (closed) | Comments |',
'| --- | ---: | ---: | ---: | ---: | ---: | ---: |',
];

let table = header.concat(rows).join('\n');
if (sortedAccounts.length > capped.length) {
table += `\n\n_And ${sortedAccounts.length - capped.length} more account(s) not shown in summary._`;
}
return table;
}

// Fetch recent PRs (up to MAX_PR)
const prs = [];
if (github.paginate?.iterator) {
Expand Down Expand Up @@ -245,7 +270,11 @@ jobs:

if (!hasAnyOpenItem) {
console.log('No open issues or PRs from new accounts; skipping alert issue.');
appendSummary('Bot Detection: flagged new accounts, but all related issues/PRs are closed. No alert issue created.');

const sorted = Array.from(highRiskAccounts.entries()).sort((a, b) => a[1].daysOld - b[1].daysOld);
appendSummary(`ℹ️ Bot Detection: ${highRiskAccounts.size} new account(s) (<${MIN_ACCOUNT_AGE_DAYS}d) found in last ${HOURS_BACK}h, but all related issues/PRs are closed. No alert issue created.`);
appendSummary('');
appendSummary(buildAccountSummaryTable(sorted, 20));
return;
}

Expand Down Expand Up @@ -319,6 +348,7 @@ jobs:
issue_number: existingIssueNumber,
body,
});
appendSummary(`🚨 Bot Detection: updated alert issue #${existingIssueNumber} (found ${highRiskAccounts.size} new account(s) in last ${HOURS_BACK}h).`);
} else {
await github.rest.issues.create({
owner: context.repo.owner,
Expand All @@ -327,6 +357,7 @@ jobs:
body,
labels: ['security', 'bot-detection'],
});
appendSummary(`🚨 Bot Detection: created alert issue (found ${highRiskAccounts.size} new account(s) in last ${HOURS_BACK}h).`);
}
} catch (e) {
console.log('Issue create/update with labels failed; retrying without labels...');
Expand All @@ -337,12 +368,18 @@ jobs:
issue_number: existingIssueNumber,
body,
});
appendSummary(`🚨 Bot Detection: updated alert issue #${existingIssueNumber} (labels failed).`);
} else {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
});
appendSummary('🚨 Bot Detection: created alert issue (labels failed).');
}
}

// Include a compact breakdown in the Step Summary for quick triage.
appendSummary('');
appendSummary(buildAccountSummaryTable(sorted, 20));