Skip to content
75 changes: 74 additions & 1 deletion .github/workflows/issue-monster.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

108 changes: 94 additions & 14 deletions .github/workflows/issue-monster.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,79 @@ jobs:
const { owner, repo } = context.repo;

try {
// Check for recent rate-limited PRs to avoid scheduling more work during rate limiting
core.info('Checking for recent rate-limited PRs...');
const rateLimitCheckDate = new Date();
rateLimitCheckDate.setHours(rateLimitCheckDate.getHours() - 1); // Check last hour
// Format as YYYY-MM-DDTHH:MM:SS for GitHub search API
const rateLimitCheckISO = rateLimitCheckDate.toISOString().split('.')[0] + 'Z';

const recentPRsQuery = `is:pr author:app/copilot-swe-agent created:>${rateLimitCheckISO} repo:${owner}/${repo}`;
const recentPRsResponse = await github.rest.search.issuesAndPullRequests({
q: recentPRsQuery,
per_page: 10,
sort: 'created',
order: 'desc'
});

core.info(`Found ${recentPRsResponse.data.total_count} recent Copilot PRs to check for rate limiting`);

// Check if any recent PRs have rate limit indicators
let rateLimitDetected = false;
for (const pr of recentPRsResponse.data.items) {
try {
const prTimelineQuery = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
timelineItems(first: 50, itemTypes: [ISSUE_COMMENT]) {
nodes {
__typename
... on IssueComment {
body
createdAt
}
}
}
}
}
}
`;

const prTimelineResult = await github.graphql(prTimelineQuery, {
owner,
repo,
number: pr.number
});

const comments = prTimelineResult?.repository?.pullRequest?.timelineItems?.nodes || [];
const rateLimitPattern = /rate limit|API rate limit|secondary rate limit|abuse detection|429|too many requests/i;

for (const comment of comments) {
if (comment.body && rateLimitPattern.test(comment.body)) {
core.warning(`Rate limiting detected in PR #${pr.number}: ${comment.body.substring(0, 200)}`);
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

Logging a substring of comment.body into workflow logs can leak unrelated or sensitive content from PR comments if the regex matches something incidental (e.g., a user mentioning “429”). Safer approach: log only that a match occurred (PR number + comment timestamp/URL), or redact/sanitize the body before emitting it.

Suggested change
core.warning(`Rate limiting detected in PR #${pr.number}: ${comment.body.substring(0, 200)}`);
const commentTimestampInfo = comment.createdAt ? ` at comment createdAt=${comment.createdAt}` : '';
core.warning(`Rate limiting indicator detected in PR #${pr.number}${commentTimestampInfo}.`);

Copilot uses AI. Check for mistakes.
rateLimitDetected = true;
break;
}
}

if (rateLimitDetected) break;
} catch (error) {
core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`);
Copy link

Copilot AI Feb 7, 2026

Choose a reason for hiding this comment

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

If the GraphQL timeline query fails due to rate limiting, this code currently just logs a warning and continues, which can lead to a false “No rate limiting detected” and proceed to perform even more API calls. Consider treating rate-limit-shaped errors (e.g., message contains "rate limit"/"secondary rate limit" or HTTP 429/403 from the error response) as a positive detection and short-circuit the workflow like the comment-body match does.

Suggested change
core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`);
const errorMessage = (error && error.message) ? error.message : String(error);
const statusCode = (error && (error.status || (error.response && error.response.status))) || undefined;
const rateLimitErrorPattern = /rate limit|secondary rate limit/i;
const isRateLimitError =
(errorMessage && rateLimitErrorPattern.test(errorMessage)) ||
statusCode === 429 ||
statusCode === 403;
if (isRateLimitError) {
core.warning(`Rate limiting detected while checking PR #${pr.number}: ${errorMessage}`);
rateLimitDetected = true;
break;
} else {
core.warning(`Could not check PR #${pr.number} for rate limiting: ${errorMessage}`);
}

Copilot uses AI. Check for mistakes.
}
}

if (rateLimitDetected) {
core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.');
core.setOutput('issue_count', 0);
core.setOutput('issue_numbers', '');
core.setOutput('issue_list', '');
core.setOutput('has_issues', 'false');
return;
}

core.info('✓ No rate limiting detected. Proceeding with issue search.');

// Labels that indicate an issue should NOT be auto-assigned
const excludeLabels = [
'wontfix',
Expand Down Expand Up @@ -326,6 +399,11 @@ Find up to three issues that need work and assign them to the Copilot agent for

The issue search has already been performed in a previous job with smart filtering and prioritization:

**Rate Limiting Protection:**
- 🛡️ **Checks for rate-limited PRs in the last hour** before scheduling new work
- If rate limiting is detected in recent Copilot PRs, the workflow skips all assignments to prevent further API issues
- Looks for patterns: "rate limit", "API rate limit", "secondary rate limit", "abuse detection", "429", "too many requests"

**Filtering Applied:**
- ✅ Only open issues **with "cookie" label** (indicating approved work queue items from automated workflows)
- ✅ Excluded issues with labels: wontfix, duplicate, invalid, question, discussion, needs-discussion, blocked, on-hold, waiting-for-feedback, needs-more-info, no-bot, no-campaign
Expand Down Expand Up @@ -469,24 +547,26 @@ safeoutputs/add_comment(item_number=<issue_number>, body="🍪 **Issue Monster h
## Success Criteria

A successful run means:
1. You reviewed the pre-searched, filtered, and prioritized issue list
2. The search already excluded issues with problematic labels (wontfix, question, discussion, etc.)
3. The search already excluded issues with campaign labels (campaign:*) as these are managed by campaign orchestrators
4. The search already excluded issues that already have assignees
5. The search already excluded issues that have sub-issues (parent/organizing issues are not tasks)
6. The search already excluded issues with closed or merged PRs (treated as complete)
7. The search already excluded issues with open PRs from Copilot agent (already being worked on)
8. Issues are sorted by priority score (good-first-issue, bug, security, etc. get higher scores)
9. For "task" or "plan" issues: You checked for parent issues and sibling sub-issue PRs if necessary
10. You selected up to three appropriate issues from the top of the priority list that are completely separate in topic
11. You read and understood each issue
12. You verified that the selected issues don't have overlapping concerns or file changes
13. You assigned each issue to the Copilot agent using `assign_to_agent`
14. You commented on each issue being assigned
1. **Rate limiting check passed** - The search verified no recent PRs are rate-limited (or workflow skipped if rate limiting detected)
2. You reviewed the pre-searched, filtered, and prioritized issue list
3. The search already excluded issues with problematic labels (wontfix, question, discussion, etc.)
4. The search already excluded issues with campaign labels (campaign:*) as these are managed by campaign orchestrators
5. The search already excluded issues that already have assignees
6. The search already excluded issues that have sub-issues (parent/organizing issues are not tasks)
7. The search already excluded issues with closed or merged PRs (treated as complete)
8. The search already excluded issues with open PRs from Copilot agent (already being worked on)
9. Issues are sorted by priority score (good-first-issue, bug, security, etc. get higher scores)
10. For "task" or "plan" issues: You checked for parent issues and sibling sub-issue PRs if necessary
11. You selected up to three appropriate issues from the top of the priority list that are completely separate in topic
12. You read and understood each issue
13. You verified that the selected issues don't have overlapping concerns or file changes
14. You assigned each issue to the Copilot agent using `assign_to_agent`
15. You commented on each issue being assigned

## Error Handling

If anything goes wrong or no work can be assigned:
- **Rate limiting detected**: The workflow automatically skips (no action needed - the search job handles this)
- **No issues found**: Use the `noop` tool with message: "🍽️ No suitable candidate issues - the plate is empty!"
- **All issues assigned**: Use the `noop` tool with message: "🍽️ All issues are already being worked on!"
- **No suitable separate issues**: Use the `noop` tool explaining which issues were considered and why they couldn't be assigned (e.g., overlapping topics, sibling PRs, etc.)
Expand Down