From 5a2c035c1fc64699cce5cbb82e97be0766679f8a Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 12:55:42 -0700 Subject: [PATCH 01/10] Add upstream Issue Monster workflow baseline --- .github/workflows/issue-monster.md | 689 ++++++++++++++++++ .../workflows/shared/github-guard-policy.md | 9 + 2 files changed, 698 insertions(+) create mode 100644 .github/workflows/issue-monster.md create mode 100644 .github/workflows/shared/github-guard-policy.md diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md new file mode 100644 index 000000000000..f93bf42fa61a --- /dev/null +++ b/.github/workflows/issue-monster.md @@ -0,0 +1,689 @@ +--- +emoji: "👾" +name: Issue Monster +description: The Cookie Monster of issues - assigns issues to Copilot coding agent one at a time +on: + workflow_dispatch: + schedule: every 30m + skip-if-match: + query: "is:pr is:open is:draft author:app/copilot-swe-agent" + max: 5 + skip-if-no-match: "is:issue is:open" + skip-if-check-failing: + include: + - build + - test + - lint-go + - lint-js + allow-pending: true + permissions: + issues: read + pull-requests: read + steps: + - name: Search for candidate issues + id: search + uses: actions/github-script@v9.0.0 + with: + script: | + const { owner, repo } = context.repo; + const MAX_ISSUES_WITH_BODY_CONTEXT = 8; + const BODY_SNIPPET_MAX_LENGTH = 600; + + 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|\b429\b|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)}`); + rateLimitDetected = true; + break; + } + } + + if (rateLimitDetected) break; + } catch (error) { + core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`); + } + } + + 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('issue_context', ''); + 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', + 'duplicate', + 'invalid', + 'question', + 'discussion', + 'needs-discussion', + 'blocked', + 'on-hold', + 'waiting-for-feedback', + 'needs-more-info', + 'no-bot', + 'no-campaign' + ]; + + // Labels that indicate an issue is a GOOD candidate for auto-assignment + const priorityLabels = [ + 'community', + 'good first issue', + 'good-first-issue', + 'bug', + 'enhancement', + 'feature', + 'documentation', + 'tech-debt', + 'refactoring', + 'performance', + 'security' + ]; + + // Search for open issues with "cookie" label and without excluded labels + // The "cookie" label indicates issues that are approved work queue items from automated workflows + const query = `is:issue is:open repo:${owner}/${repo} label:cookie -label:"${excludeLabels.join('" -label:"')}"`; + core.info(`Searching: ${query}`); + const response = await github.rest.search.issuesAndPullRequests({ + q: query, + per_page: 100, + sort: 'created', + order: 'desc' + }); + core.info(`Found ${response.data.total_count} total issues matching basic criteria`); + + // Fetch full details for each issue to get labels, assignees, sub-issues, and linked PRs + // Track integrity-filtered issues to emit a diagnostic summary + const integrityFilteredIssues = []; + const issuesWithDetails = (await Promise.all( + response.data.items.map(async (issue) => { + // Fetch full issue details — some issues may be blocked by integrity policy + let fullIssue; + try { + fullIssue = await github.rest.issues.get({ + owner, + repo, + issue_number: issue.number + }); + } catch (fetchError) { + // Integrity-filtered issues (403/451) or other transient errors should be + // skipped individually rather than failing the entire batch + const status = fetchError.status || fetchError.response?.status; + // 403 = Forbidden (integrity policy), 451 = Unavailable For Legal Reasons + const isIntegrityBlock = status === 403 || status === 451 || + /\bintegrity\b/i.test(fetchError.message || ''); + const errorSummary = (fetchError.message || String(fetchError)).slice(0, 120); + if (isIntegrityBlock) { + integrityFilteredIssues.push(issue.number); + core.warning(`⚠️ Skipping issue #${issue.number}: blocked by integrity policy (HTTP ${status || 'unknown'}): ${errorSummary}`); + } else { + core.warning(`⚠️ Skipping issue #${issue.number}: could not fetch details (HTTP ${status || 'unknown'}): ${errorSummary}`); + } + return null; + } + + // Check if this issue has sub-issues and linked PRs using GraphQL + let subIssuesCount = 0; + let linkedPRs = []; + try { + const issueDetailsQuery = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + subIssues { + totalCount + } + timelineItems(first: 100, itemTypes: [CROSS_REFERENCED_EVENT]) { + nodes { + ... on CrossReferencedEvent { + source { + __typename + ... on PullRequest { + number + state + isDraft + author { + login + } + } + } + } + } + } + } + } + } + `; + const issueDetailsResult = await github.graphql(issueDetailsQuery, { + owner, + repo, + number: issue.number + }); + + subIssuesCount = issueDetailsResult?.repository?.issue?.subIssues?.totalCount || 0; + + // Extract linked PRs from timeline + const timelineItems = issueDetailsResult?.repository?.issue?.timelineItems?.nodes || []; + linkedPRs = timelineItems + .filter(item => item?.source?.__typename === 'PullRequest') + .map(item => ({ + number: item.source.number, + state: item.source.state, + isDraft: item.source.isDraft, + author: item.source.author?.login + })); + + core.info(`Issue #${issue.number} has ${linkedPRs.length} linked PR(s)`); + } catch (error) { + // If GraphQL query fails, continue with defaults + core.warning(`Could not check details for #${issue.number}: ${error.message}`); + } + + return { + ...fullIssue.data, + subIssuesCount, + linkedPRs + }; + }) + )).filter(Boolean); // Remove null entries (integrity-filtered or otherwise skipped) + + // Emit diagnostic summary for integrity-filtered issues + if (integrityFilteredIssues.length > 0) { + core.warning(`🛡️ Integrity filter diagnostic: ${integrityFilteredIssues.length} issue(s) were skipped due to integrity policy: #${integrityFilteredIssues.join(', #')}. These issues will be excluded from this run.`); + } + + // Filter and score issues + const scoredIssues = issuesWithDetails + .filter(issue => { + // Exclude issues that already have assignees + if (issue.assignees && issue.assignees.length > 0) { + core.info(`Skipping #${issue.number}: already has assignees`); + return false; + } + + // Exclude issues with excluded labels (double check) + const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + if (issueLabels.some(label => excludeLabels.map(l => l.toLowerCase()).includes(label))) { + core.info(`Skipping #${issue.number}: has excluded label`); + return false; + } + + // Exclude issues with campaign labels (campaign:*) + // Campaign items are managed by campaign orchestrators + if (issueLabels.some(label => label.startsWith('campaign:'))) { + core.info(`Skipping #${issue.number}: has campaign label (managed by campaign orchestrator)`); + return false; + } + + // Exclude issues that have sub-issues (parent/organizing issues) + if (issue.subIssuesCount > 0) { + core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); + return false; + } + + // Exclude issues with closed PRs (treat as complete) + const closedPRs = issue.linkedPRs?.filter(pr => pr.state === 'CLOSED' || pr.state === 'MERGED') || []; + if (closedPRs.length > 0) { + core.info(`Skipping #${issue.number}: has ${closedPRs.length} closed/merged PR(s) - treating as complete`); + return false; + } + + // Exclude issues with open PRs from Copilot coding agent + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && + (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) + ) || []; + if (openCopilotPRs.length > 0) { + core.info(`Skipping #${issue.number}: has ${openCopilotPRs.length} open PR(s) from Copilot - already being worked on`); + return false; + } + + return true; + }) + .map(issue => { + const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + let score = 0; + + // Score based on priority labels (higher score = higher priority) + // Community issues always get the highest priority — these are + // requests from external contributors and should be addressed first. + if (issueLabels.includes('community')) { + score += 60; + } + if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { + score += 50; + } + if (issueLabels.includes('bug')) { + score += 40; + } + if (issueLabels.includes('security')) { + score += 45; + } + if (issueLabels.includes('documentation')) { + score += 35; + } + if (issueLabels.includes('enhancement') || issueLabels.includes('feature')) { + score += 30; + } + if (issueLabels.includes('performance')) { + score += 25; + } + if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { + score += 20; + } + + // Bonus for issues with clear labels (any priority label) + if (issueLabels.some(label => priorityLabels.map(l => l.toLowerCase()).includes(label))) { + score += 10; + } + + // Age bonus: older issues get slight priority (days old / 10) + const ageInDays = Math.floor((Date.now() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); + score += Math.min(ageInDays / 10, 20); // Cap age bonus at 20 points + + return { + number: issue.number, + title: issue.title, + labels: issue.labels.map(l => l.name), + body: issue.body, + created_at: issue.created_at, + score + }; + }) + .sort((a, b) => b.score - a.score); // Sort by score descending + + // Format output + const issueList = scoredIssues.map(i => { + const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; + return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + }).join('\n'); + + // Pre-fetch compact body context for top candidates so the agent can + // triage without extra reads in most runs. + const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + const body = (i.body || '').replace(/\s+/g, ' ').trim(); + const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; + const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; + return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + }).join('\n\n---\n\n'); + + const issueNumbers = scoredIssues.map(i => i.number).join(','); + + core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); + if (scoredIssues.length > 0) { + core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); + } + + core.setOutput('issue_count', scoredIssues.length); + core.setOutput('issue_numbers', issueNumbers); + core.setOutput('issue_list', issueList); + core.setOutput('issue_context', issueContext); + + if (scoredIssues.length === 0) { + core.info('🍽️ No suitable candidate issues - the plate is empty!'); + core.setOutput('has_issues', 'false'); + } else { + core.setOutput('has_issues', 'true'); + } + } catch (error) { + core.error(`Error searching for issues: ${error.message}`); + core.setOutput('issue_count', 0); + core.setOutput('issue_numbers', ''); + core.setOutput('issue_list', ''); + core.setOutput('issue_context', ''); + core.setOutput('has_issues', 'false'); + } + + +permissions: + contents: read + issues: read + pull-requests: read + copilot-requests: write + +sandbox: + agent: + sudo: false + +engine: + id: pi + model: copilot/gpt-5.4 + +imports: + - shared/github-guard-policy.md + - shared/activation-app.md + + - shared/otlp.md +timeout-minutes: 30 + +tools: + cli-proxy: true + github: + mode: gh-proxy + min-integrity: approved + toolsets: [issues] + +if: needs.pre_activation.outputs.has_issues == 'true' + +jobs: + pre-activation: + outputs: + issue_count: ${{ steps.search.outputs.issue_count }} + issue_numbers: ${{ steps.search.outputs.issue_numbers }} + issue_list: ${{ steps.search.outputs.issue_list }} + issue_context: ${{ steps.search.outputs.issue_context }} + has_issues: ${{ steps.search.outputs.has_issues }} + +safe-outputs: + assign-to-agent: + max: 3 + target: "*" # Requires explicit issue_number in agent output + allowed: [copilot] # Only allow copilot agent + ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable + add-comment: + max: 3 + target: "*" + messages: + footer: "> 🍪 *Om nom nom by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}" + run-started: "🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom..." + run-success: "🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋" + run-failure: "🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢" +--- + +{{#runtime-import? .github/shared-instructions.md}} + +# Issue Monster 🍪 + +You are the **Issue Monster** - the Cookie Monster of issues! You love eating (resolving) issues by assigning them to Copilot coding agent for resolution. + +## Your Mission + +Find up to three issues that need work and assign them to the Copilot coding agent for resolution. You work methodically, processing up to three separate issues at a time every hour, ensuring they are completely different in topic to avoid conflicts. + +## Current Context + +- **Repository**: ${{ github.repository }} +- **Run Time**: $(date -u +"%Y-%m-%d %H:%M:%S UTC") +- Apply inline skills `issue-monster-token-budget` and `issue-monster-report-formatting` for budget and report-shape constraints. + +## Step-by-Step Process + +### 1. Review Pre-Searched and Prioritized Issue List + +The issue search has already been performed in the pre-activation 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 +- ✅ Excluded issues with campaign labels (campaign:*) - these are managed by campaign orchestrators +- ✅ Excluded issues that already have assignees +- ✅ Excluded issues that have sub-issues (parent/organizing issues) +- ✅ Excluded issues with closed or merged PRs (treating those as complete) +- ✅ Excluded issues with open PRs from Copilot coding agent (already being worked on) +- ✅ Prioritized issues with labels: good-first-issue, bug, security, documentation, enhancement, feature, performance, tech-debt, refactoring + +**Scoring System:** +Issues are scored and sorted by priority: +- **Community**: +60 points *(always highest — issues from external contributors)* +- Good first issue: +50 points +- Security: +45 points +- Bug: +40 points +- Documentation: +35 points +- Enhancement/Feature: +30 points +- Performance: +25 points +- Tech-debt/Refactoring: +20 points +- Has any priority label: +10 points +- Age bonus: +0-20 points (older issues get slight priority) + +**Issue Count**: ${{ needs.pre_activation.outputs.issue_count }} +**Issue Numbers**: ${{ needs.pre_activation.outputs.issue_numbers }} + +**Available Issues (sorted by priority score):** +``` +${{ needs.pre_activation.outputs.issue_list }} +``` + +**Pre-fetched Body Context (top candidates):** +``` +${{ needs.pre_activation.outputs.issue_context }} +``` + +Work with this pre-fetched, filtered, and prioritized list of issues. Do not perform additional searches - candidate issue numbers and body excerpts are already identified above. + +### 1a. Handle Parent-Child Issue Relationships (for "task" or "plan" labeled issues) + +For issues with the "task" or "plan" label, check if they are sub-issues linked to a parent issue: + +1. **Identify if the issue is a sub-issue**: Check if the issue has a parent issue link (via GitHub's sub-issue feature or by parsing the issue body for parent references like "Parent: #123" or "Part of #123") + +2. **If the issue has a parent issue**: + - Fetch the parent issue to understand the full context + - List all sibling sub-issues (other sub-issues of the same parent) + - **Check for existing sibling PRs**: If any sibling sub-issue already has an open PR from Copilot, **skip this issue** and move to the next candidate + - Process sub-issues in order of their creation date (oldest first) + +3. **Only one sub-issue sibling PR at a time**: If a sibling sub-issue already has an open draft PR from Copilot, skip all other siblings until that PR is merged or closed + +**Example**: If parent issue #100 has sub-issues #101, #102, #103: +- If #101 has an open PR, skip #102 and #103 +- Only after #101's PR is merged/closed, process #102 +- This ensures orderly, sequential processing of related tasks + +### 2. Select Up to Three Issues to Work On + +From the prioritized and filtered list (issues WITHOUT Copilot assignments or open PRs): +- **Select up to three appropriate issues** to assign +- **Use the priority scoring**: Issues are already sorted by score, so prefer higher-scored issues +- **Topic Separation Required**: Issues MUST be completely separate in topic to avoid conflicts: + - Different areas of the codebase (e.g., one CLI issue, one workflow issue, one docs issue) + - Different features or components + - No overlapping file changes expected + - Different problem domains +- **Priority Guidelines**: + - Start from the top of the sorted list (highest scores) + - Skip issues that would conflict with already-selected issues + - For "task" sub-issues: Process in order (oldest first among siblings) + - Clearly independent from each other + +**Topic Separation Examples:** +- ✅ **GOOD**: Issue about CLI flags + Issue about documentation + Issue about workflow syntax +- ✅ **GOOD**: Issue about error messages + Issue about performance optimization + Issue about test coverage +- ❌ **BAD**: Two issues both modifying the same file or feature +- ❌ **BAD**: Issues that are part of the same larger task or feature +- ❌ **BAD**: Related issues that might have conflicting changes + +**If all issues are already being worked on:** +- Use the `noop` tool to explain why no work was assigned: + ``` + safeoutputs/noop(message="🍽️ All issues are already being worked on!") + ``` +- **STOP** and do not proceed further + +**If fewer than 3 suitable separate issues are available:** +- Assign only the issues that are clearly separate in topic +- Do not force assignments just to reach the maximum + +### 3. Validate Selected Issues (Body-First) + +For each selected issue (which has already been pre-filtered to ensure no open/closed PRs exist): +- Use the pre-fetched body context first +- If a body excerpt is ambiguous, call `issue_read` with `method: get` for that issue +- Do **not** fetch comments by default +- Only fetch comments (`issue_read` with `method: get_comments`) when a specific triage rule truly requires comment context (for example: to confirm whether maintainers already requested a specific implementation approach, or to capture additional repro steps posted after the original issue body) +- Understand what fix is needed +- Identify the files that need to be modified +- Verify it doesn't overlap with the other selected issues + +#### Handling Integrity-Blocked Issues + +Some issues may be blocked by an integrity policy when you try to read them with `issue_read`. If `issue_read` returns an error mentioning "integrity", "policy", "forbidden", or returns HTTP 403/451: +- **Do NOT call `missing_data`** - this would fail the entire run +- **Skip that issue silently** and remove it from your working list +- **Track it** in your internal notes as "integrity-blocked" +- **Continue** with the next candidate from the pre-filtered list +- At the end, **include a one-line diagnostic** in your `noop` message if any issues were skipped this way + +**Partial filtering example**: Issues #100, #102, #105 selected; #102 is integrity-blocked. +→ Assign #100 and #105, then call `noop` with: `"Assigned #100 and #105. Skipped #102 (integrity-filtered)."` + +**Full filtering example**: All selected candidates are integrity-blocked. +→ Call `noop` with: `"🛡️ All 3 candidates (#100, #102, #105) were integrity-filtered. No assignments made this run."` + + +### 4. Assign Issues to Copilot Agent + +For each selected issue, use the `assign_to_agent` tool from the `safeoutputs` MCP server to assign the Copilot coding agent: + +``` +safeoutputs/assign_to_agent(issue_number=, agent="copilot") +``` + +Use the exact field name `issue_number` (underscore). Do **not** use `issue-number` (hyphen), which is invalid and will fail safe-output validation. + +**Important**: Only call `assign_to_agent` for **issues**, never for pull requests. The pre-fetched list already contains only issues, so never pass a PR number here. If you are ever unsure whether a number refers to an issue or a PR, call `issue_read` with `method: get` and check: if the response includes a `pull_request` URL field, skip that item. + +Do not use GitHub tools for this assignment. The `assign_to_agent` tool will handle the actual assignment. + +The Copilot coding agent will: +1. Analyze the issue and related context +2. Generate the necessary code changes +3. Create a pull request with the fix +4. Follow the repository's AGENTS.md guidelines + +### 5. Add Comment to Each Assigned Issue + +For each issue you assign, use the `add_comment` tool from the `safeoutputs` MCP server to add a comment: + +``` +safeoutputs/add_comment(item_number=, body="🍪 **Issue Monster selected this for Copilot**\n\nI've identified this issue as a good candidate for automated resolution and requested assignment to the Copilot coding agent.\n\nIf assignment succeeds, the Copilot coding agent will analyze the issue and create a pull request with the fix.\n\nOm nom nom! 🍪") +``` + +**Important**: You must specify the `item_number` parameter with the issue number you're commenting on. This workflow runs on a schedule without a triggering issue, so the target must be explicitly specified. + +## skill: `issue-monster-token-budget` +--- +description: Keeps recurring issue-monster runs lean and bounded. +--- + +Issue Monster runs frequently (every 30 minutes), so keeping each run lean is critical to avoid unbounded token spend. + +- **Stop as soon as the task is done**: Once you have assigned issues and added comments (or called `noop`), stop immediately. Do not produce additional analysis, summaries, or commentary. +- **Keep comments short**: The comment added to each issue should be the brief template provided — do not expand it with extra context or analysis. +- **Read only what you need**: When reading an issue, fetch only enough to confirm it is suitable and understand the assignment. Do not read every comment thread unless needed to resolve a conflict. +- **Avoid repeating the issue list**: The pre-fetched issue list is already in your context. Do not make additional API calls to fetch the list again, and do not generate a summary of the entire list. +- **One tool call per action**: Assign and comment in two calls per issue. Do not make extra verification calls after a successful assignment. + +**Target tokens/run**: 50K–150K +**Alert threshold**: >300K tokens + +## Important Guidelines + +- ✅ **Up to three at a time**: Assign up to three issues per run, but only if they are completely separate in topic +- ✅ **Topic separation is critical**: Never assign issues that might have overlapping changes or related work +- ✅ **Be transparent**: Comment on each issue being assigned +- ✅ **Check assignments**: Skip issues already assigned to Copilot +- ✅ **Sibling awareness**: For "task" or "plan" sub-issues, skip if any sibling already has an open Copilot PR +- ✅ **Process in order**: For sub-issues of the same parent, process oldest first +- ✅ **Always report outcome**: If no issues are assigned, use the `noop` tool to explain why +- ✅ **Skip integrity-blocked issues**: If `issue_read` is blocked by integrity policy, skip that issue and continue — never call `missing_data` for integrity errors +- ❌ **Don't force batching**: If only 1-2 clearly separate issues exist, assign only those +- ❌ **Never assign pull requests**: `assign_to_agent` is for issues only — never pass a PR number + +## skill: `issue-monster-report-formatting` +--- +description: Defines report formatting and progressive disclosure rules. +--- + +- **Header Levels**: Use h3 (`###`) or lower for all headers in your report to maintain proper document hierarchy. Never use h1 (`#`) or h2 (`##`) headers. +- **Progressive Disclosure**: Wrap long sections or verbose details in `
Section Name` tags to improve readability and reduce scrolling. +- Keep critical information visible (summary, key outcomes, and recommendations) and use collapsible sections for secondary details. + +### Recommended Report Structure + +1. **Overview**: 1-2 paragraphs summarizing key findings (always visible) +2. **Critical Information**: Key metrics, status, critical issues (always visible) +3. **Details**: Use `
Section Name` for expanded content +4. **Recommendations**: Actionable next steps (always visible) + +## Success Criteria + +A successful run means: +1. You used the pre-fetched prioritized list (and body context) without re-searching +2. You selected up to three issues that are clearly separate in topic +3. You used body-first validation and only fetched comments when strictly necessary +4. You assigned each selected issue to Copilot using `assign_to_agent` +5. You commented on each assigned issue (or called `noop` when no assignments were made) + +## Error Handling + +If anything goes wrong or no work can be assigned: +- **Rate limiting detected**: The workflow automatically skips (no action needed - the pre-activation 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.) +- **Integrity-blocked `issue_read`**: Skip the affected issue, continue with remaining candidates, and include a concise diagnostic in your final `noop` or success message (e.g., "Skipped #NNN (integrity-filtered)."). **Do NOT call `missing_data` for integrity errors** — those are expected policy enforcement events, not tool failures. +- **Unexpected API errors** (non-integrity): Use the `missing_data` tool to report the issue + +**CRITICAL**: You MUST call at least one safe output tool every run. If you don't assign any issues, you MUST call the `noop` tool to explain why. Never complete a run without making at least one tool call. + +Remember: You're the Issue Monster! Stay hungry, work methodically, and let Copilot do the heavy lifting! 🍪 Om nom nom! \ No newline at end of file diff --git a/.github/workflows/shared/github-guard-policy.md b/.github/workflows/shared/github-guard-policy.md new file mode 100644 index 000000000000..e65eb2677995 --- /dev/null +++ b/.github/workflows/shared/github-guard-policy.md @@ -0,0 +1,9 @@ +--- +# Shared GitHub guard policy for the gh-aw repository. +# Provides the standard approval labels that allow issues and PRs from +# community contributors to bypass the min-integrity check when the +# GitHub MCP server performs tool calls. +tools: + github: + approval-labels: [cookie, community] +--- From a7f72db8cbd1020611bcf3a72b48d5055a74f2b7 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 12:55:52 -0700 Subject: [PATCH 02/10] Remove unused Issue Monster imports --- .github/workflows/issue-monster.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index f93bf42fa61a..97aed88188f7 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -406,9 +406,6 @@ engine: imports: - shared/github-guard-policy.md - - shared/activation-app.md - - - shared/otlp.md timeout-minutes: 30 tools: From 6070a6058f1261f8d0432c1faf08b047767d99f0 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 12:56:34 -0700 Subject: [PATCH 03/10] Adapt Issue Monster labels for SDK --- .github/workflows/issue-monster.md | 55 +++++-------------- .../workflows/shared/github-guard-policy.md | 9 ++- 2 files changed, 18 insertions(+), 46 deletions(-) diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 97aed88188f7..355de70ba18b 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -116,23 +116,18 @@ on: 'on-hold', 'waiting-for-feedback', 'needs-more-info', - 'no-bot', - 'no-campaign' + 'no-bot' ]; // Labels that indicate an issue is a GOOD candidate for auto-assignment const priorityLabels = [ - 'community', 'good first issue', 'good-first-issue', 'bug', - 'enhancement', - 'feature', 'documentation', 'tech-debt', 'refactoring', - 'performance', - 'security' + 'performance' ]; // Search for open issues with "cookie" label and without excluded labels @@ -263,13 +258,6 @@ on: return false; } - // Exclude issues with campaign labels (campaign:*) - // Campaign items are managed by campaign orchestrators - if (issueLabels.some(label => label.startsWith('campaign:'))) { - core.info(`Skipping #${issue.number}: has campaign label (managed by campaign orchestrator)`); - return false; - } - // Exclude issues that have sub-issues (parent/organizing issues) if (issue.subIssuesCount > 0) { core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); @@ -300,31 +288,20 @@ on: let score = 0; // Score based on priority labels (higher score = higher priority) - // Community issues always get the highest priority — these are - // requests from external contributors and should be addressed first. - if (issueLabels.includes('community')) { - score += 60; - } if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { - score += 50; - } - if (issueLabels.includes('bug')) { score += 40; } - if (issueLabels.includes('security')) { - score += 45; + if (issueLabels.includes('bug')) { + score += 55; } if (issueLabels.includes('documentation')) { - score += 35; - } - if (issueLabels.includes('enhancement') || issueLabels.includes('feature')) { - score += 30; + score += 60; } if (issueLabels.includes('performance')) { - score += 25; + score += 30; } if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { - score += 20; + score += 45; } // Bonus for issues with clear labels (any priority label) @@ -471,24 +448,20 @@ The issue search has already been performed in the pre-activation job with smart **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 -- ✅ Excluded issues with campaign labels (campaign:*) - these are managed by campaign orchestrators +- ✅ Excluded issues with labels: wontfix, duplicate, invalid, question, discussion, needs-discussion, blocked, on-hold, waiting-for-feedback, needs-more-info, no-bot - ✅ Excluded issues that already have assignees - ✅ Excluded issues that have sub-issues (parent/organizing issues) - ✅ Excluded issues with closed or merged PRs (treating those as complete) - ✅ Excluded issues with open PRs from Copilot coding agent (already being worked on) -- ✅ Prioritized issues with labels: good-first-issue, bug, security, documentation, enhancement, feature, performance, tech-debt, refactoring +- ✅ Prioritized issues with labels: documentation, bug, tech-debt, refactoring, good-first-issue, performance **Scoring System:** Issues are scored and sorted by priority: -- **Community**: +60 points *(always highest — issues from external contributors)* -- Good first issue: +50 points -- Security: +45 points -- Bug: +40 points -- Documentation: +35 points -- Enhancement/Feature: +30 points -- Performance: +25 points -- Tech-debt/Refactoring: +20 points +- Documentation: +60 points +- Bug: +55 points +- Tech-debt/Refactoring: +45 points +- Good first issue: +40 points +- Performance: +30 points - Has any priority label: +10 points - Age bonus: +0-20 points (older issues get slight priority) diff --git a/.github/workflows/shared/github-guard-policy.md b/.github/workflows/shared/github-guard-policy.md index e65eb2677995..495ae9e1a4e3 100644 --- a/.github/workflows/shared/github-guard-policy.md +++ b/.github/workflows/shared/github-guard-policy.md @@ -1,9 +1,8 @@ --- -# Shared GitHub guard policy for the gh-aw repository. -# Provides the standard approval labels that allow issues and PRs from -# community contributors to bypass the min-integrity check when the -# GitHub MCP server performs tool calls. +# Shared GitHub guard policy for the SDK repository. +# Provides the approval label that allows cookie-approved issues to bypass the +# min-integrity check when the GitHub MCP server performs tool calls. tools: github: - approval-labels: [cookie, community] + approval-labels: [cookie] --- From caa8b87e876b0426118d4ce2231ef81f574b6337 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 12:57:06 -0700 Subject: [PATCH 04/10] Remove gh-aw-specific Issue Monster assumptions --- .github/workflows/issue-monster.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 355de70ba18b..66874fad4be1 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -9,13 +9,6 @@ on: query: "is:pr is:open is:draft author:app/copilot-swe-agent" max: 5 skip-if-no-match: "is:issue is:open" - skip-if-check-failing: - include: - - build - - test - - lint-go - - lint-js - allow-pending: true permissions: issues: read pull-requests: read @@ -378,15 +371,13 @@ sandbox: sudo: false engine: - id: pi - model: copilot/gpt-5.4 + id: copilot imports: - shared/github-guard-policy.md timeout-minutes: 30 tools: - cli-proxy: true github: mode: gh-proxy min-integrity: approved From 35ec01540e77f09df2ced3440af7c6112b82ec71 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 13:02:52 -0700 Subject: [PATCH 05/10] Add SDK Issue Monster controls --- .github/aw/actions-lock.json | 6 +- .github/workflows/issue-monster.lock.yml | 2394 ++++++++++++++++++++++ .github/workflows/issue-monster.md | 21 +- 3 files changed, 2415 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/issue-monster.lock.yml diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 5d7a62fd96f4..2991d3a563d1 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -25,10 +25,10 @@ "version": "v7.0.1", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup@v0.79.8": { + "github/gh-aw-actions/setup@v0.81.6": { "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" + "version": "v0.81.6", + "sha": "ba6380cc6e5be5d21677bebe04d52fb48e3abec7" } } } diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml new file mode 100644 index 000000000000..1a3596f3a4a0 --- /dev/null +++ b/.github/workflows/issue-monster.lock.yml @@ -0,0 +1,2394 @@ +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1b42d22ad64f7beba3ea7ab8bfd35c3826b8f92630e066259cf0334e83302c04","body_hash":"c116fcbe4c9c69238d152761ddaee59ea90ae3e49da718bf9dd6285e828a3520","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# The Cookie Monster of issues - assigns issues to Copilot coding agent one at a time +# +# Resolved workflow manifest: +# Imports: +# - shared/github-guard-policy.md +# +# Secrets used: +# - GH_AW_AGENT_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d +# - ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d +# - ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 + +name: "Issue Monster" +on: + # permissions: # Permissions applied to pre-activation job + # issues: read + # pull-requests: read + schedule: + - cron: "46 */12 * * *" + # Friendly format: every 12h (scattered) + # skip-if-match: # Skip-if-match processed as search check in pre-activation job + # max: 5 + # query: is:pr is:open is:draft author:app/copilot-swe-agent + # skip-if-no-match: is:issue is:open # Skip-if-no-match processed as search check in pre-activation job + # steps: # Steps injected into pre-activation job + # - id: search + # name: Search for candidate issues + # uses: actions/github-script@v9.0.0 + # with: + # script: | + # const { owner, repo } = context.repo; + # const MAX_ISSUES_WITH_BODY_CONTEXT = 8; + # const BODY_SNIPPET_MAX_LENGTH = 600; + # + # 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|\b429\b|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)}`); + # rateLimitDetected = true; + # break; + # } + # } + # + # if (rateLimitDetected) break; + # } catch (error) { + # core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`); + # } + # } + # + # 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('issue_context', ''); + # 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', + # 'duplicate', + # 'invalid', + # 'question', + # 'discussion', + # 'needs-discussion', + # 'blocked', + # 'on-hold', + # 'waiting-for-feedback', + # 'needs-more-info', + # 'no-bot' + # ]; + # + # // Labels that indicate an issue is a GOOD candidate for auto-assignment + # const priorityLabels = [ + # 'good first issue', + # 'good-first-issue', + # 'bug', + # 'documentation', + # 'tech-debt', + # 'refactoring', + # 'performance' + # ]; + # + # // Search for open issues with "cookie" label and without excluded labels + # // The "cookie" label indicates issues that are approved work queue items from automated workflows + # const query = `is:issue is:open repo:${owner}/${repo} label:cookie -label:"${excludeLabels.join('" -label:"')}"`; + # core.info(`Searching: ${query}`); + # const response = await github.rest.search.issuesAndPullRequests({ + # q: query, + # per_page: 100, + # sort: 'created', + # order: 'desc' + # }); + # core.info(`Found ${response.data.total_count} total issues matching basic criteria`); + # + # // Fetch full details for each issue to get labels, assignees, sub-issues, and linked PRs + # // Track integrity-filtered issues to emit a diagnostic summary + # const integrityFilteredIssues = []; + # const issuesWithDetails = (await Promise.all( + # response.data.items.map(async (issue) => { + # // Fetch full issue details — some issues may be blocked by integrity policy + # let fullIssue; + # try { + # fullIssue = await github.rest.issues.get({ + # owner, + # repo, + # issue_number: issue.number + # }); + # } catch (fetchError) { + # // Integrity-filtered issues (403/451) or other transient errors should be + # // skipped individually rather than failing the entire batch + # const status = fetchError.status || fetchError.response?.status; + # // 403 = Forbidden (integrity policy), 451 = Unavailable For Legal Reasons + # const isIntegrityBlock = status === 403 || status === 451 || + # /\bintegrity\b/i.test(fetchError.message || ''); + # const errorSummary = (fetchError.message || String(fetchError)).slice(0, 120); + # if (isIntegrityBlock) { + # integrityFilteredIssues.push(issue.number); + # core.warning(`⚠️ Skipping issue #${issue.number}: blocked by integrity policy (HTTP ${status || 'unknown'}): ${errorSummary}`); + # } else { + # core.warning(`⚠️ Skipping issue #${issue.number}: could not fetch details (HTTP ${status || 'unknown'}): ${errorSummary}`); + # } + # return null; + # } + # + # // Check if this issue has sub-issues and linked PRs using GraphQL + # let subIssuesCount = 0; + # let linkedPRs = []; + # try { + # const issueDetailsQuery = ` + # query($owner: String!, $repo: String!, $number: Int!) { + # repository(owner: $owner, name: $repo) { + # issue(number: $number) { + # subIssues { + # totalCount + # } + # timelineItems(first: 100, itemTypes: [CROSS_REFERENCED_EVENT]) { + # nodes { + # ... on CrossReferencedEvent { + # source { + # __typename + # ... on PullRequest { + # number + # state + # isDraft + # author { + # login + # } + # } + # } + # } + # } + # } + # } + # } + # } + # `; + # const issueDetailsResult = await github.graphql(issueDetailsQuery, { + # owner, + # repo, + # number: issue.number + # }); + # + # subIssuesCount = issueDetailsResult?.repository?.issue?.subIssues?.totalCount || 0; + # + # // Extract linked PRs from timeline + # const timelineItems = issueDetailsResult?.repository?.issue?.timelineItems?.nodes || []; + # linkedPRs = timelineItems + # .filter(item => item?.source?.__typename === 'PullRequest') + # .map(item => ({ + # number: item.source.number, + # state: item.source.state, + # isDraft: item.source.isDraft, + # author: item.source.author?.login + # })); + # + # core.info(`Issue #${issue.number} has ${linkedPRs.length} linked PR(s)`); + # } catch (error) { + # // If GraphQL query fails, continue with defaults + # core.warning(`Could not check details for #${issue.number}: ${error.message}`); + # } + # + # return { + # ...fullIssue.data, + # subIssuesCount, + # linkedPRs + # }; + # }) + # )).filter(Boolean); // Remove null entries (integrity-filtered or otherwise skipped) + # + # // Emit diagnostic summary for integrity-filtered issues + # if (integrityFilteredIssues.length > 0) { + # core.warning(`🛡️ Integrity filter diagnostic: ${integrityFilteredIssues.length} issue(s) were skipped due to integrity policy: #${integrityFilteredIssues.join(', #')}. These issues will be excluded from this run.`); + # } + # + # // Filter and score issues + # const scoredIssues = issuesWithDetails + # .filter(issue => { + # // Exclude issues that already have assignees + # if (issue.assignees && issue.assignees.length > 0) { + # core.info(`Skipping #${issue.number}: already has assignees`); + # return false; + # } + # + # // Exclude issues with excluded labels (double check) + # const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + # if (issueLabels.some(label => excludeLabels.map(l => l.toLowerCase()).includes(label))) { + # core.info(`Skipping #${issue.number}: has excluded label`); + # return false; + # } + # + # // Exclude issues that have sub-issues (parent/organizing issues) + # if (issue.subIssuesCount > 0) { + # core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); + # return false; + # } + # + # // Exclude issues with closed PRs (treat as complete) + # const closedPRs = issue.linkedPRs?.filter(pr => pr.state === 'CLOSED' || pr.state === 'MERGED') || []; + # if (closedPRs.length > 0) { + # core.info(`Skipping #${issue.number}: has ${closedPRs.length} closed/merged PR(s) - treating as complete`); + # return false; + # } + # + # // Exclude issues with open PRs from Copilot coding agent + # const openCopilotPRs = issue.linkedPRs?.filter(pr => + # pr.state === 'OPEN' && + # (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) + # ) || []; + # if (openCopilotPRs.length > 0) { + # core.info(`Skipping #${issue.number}: has ${openCopilotPRs.length} open PR(s) from Copilot - already being worked on`); + # return false; + # } + # + # return true; + # }) + # .map(issue => { + # const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + # let score = 0; + # + # // Score based on priority labels (higher score = higher priority) + # if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { + # score += 40; + # } + # if (issueLabels.includes('bug')) { + # score += 55; + # } + # if (issueLabels.includes('documentation')) { + # score += 60; + # } + # if (issueLabels.includes('performance')) { + # score += 30; + # } + # if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { + # score += 45; + # } + # + # // Bonus for issues with clear labels (any priority label) + # if (issueLabels.some(label => priorityLabels.map(l => l.toLowerCase()).includes(label))) { + # score += 10; + # } + # + # // Age bonus: older issues get slight priority (days old / 10) + # const ageInDays = Math.floor((Date.now() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); + # score += Math.min(ageInDays / 10, 20); // Cap age bonus at 20 points + # + # return { + # number: issue.number, + # title: issue.title, + # labels: issue.labels.map(l => l.name), + # body: issue.body, + # created_at: issue.created_at, + # score + # }; + # }) + # .sort((a, b) => b.score - a.score); // Sort by score descending + # + # // Format output + # const issueList = scoredIssues.map(i => { + # const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; + # return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + # }).join('\n'); + # + # // Pre-fetch compact body context for top candidates so the agent can + # // triage without extra reads in most runs. + # const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + # const body = (i.body || '').replace(/\s+/g, ' ').trim(); + # const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; + # const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; + # return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + # }).join('\n\n---\n\n'); + # + # const issueNumbers = scoredIssues.map(i => i.number).join(','); + # + # core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); + # if (scoredIssues.length > 0) { + # core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); + # } + # + # core.setOutput('issue_count', scoredIssues.length); + # core.setOutput('issue_numbers', issueNumbers); + # core.setOutput('issue_list', issueList); + # core.setOutput('issue_context', issueContext); + # + # if (scoredIssues.length === 0) { + # core.info('🍽️ No suitable candidate issues - the plate is empty!'); + # core.setOutput('has_issues', 'false'); + # } else { + # core.setOutput('has_issues', 'true'); + # } + # } catch (error) { + # core.error(`Error searching for issues: ${error.message}`); + # core.setOutput('issue_count', 0); + # core.setOutput('issue_numbers', ''); + # core.setOutput('issue_list', ''); + # core.setOutput('issue_context', ''); + # core.setOutput('has_issues', 'false'); + # } + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + base_branch: + default: main + description: Base branch for Copilot PRs. Use main for .NET 11, release/* for servicing, and release/dnup for dotnetup. + required: true + type: string + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.discussion.number || github.run_id }}" + +run-name: "Issue Monster" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' && (needs.pre_activation.outputs.has_issues == 'true') + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + comment_id: "" + comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AGENT_VERSION: "1.0.65" + GH_AW_INFO_CLI_VERSION: "v0.81.6" + GH_AW_INFO_WORKFLOW_NAME: "Issue Monster" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_INFO_FRONTMATTER_EMOJI: "👾" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuemonster-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuemonster- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Restore daily AIC usage cache (artifact fallback) + id: restore-daily-aic-cache-fallback + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }} + GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/restore_aic_usage_cache_fallback.cjs'); + await main(); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_ID: "issue-monster" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_HAS_SLASH_COMMAND: "false" + GH_AW_HAS_LABEL_COMMAND: "false" + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "issue-monster.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.81.6" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Log runtime features + if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh" + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_NUMBERS: ${{ needs.pre_activation.outputs.issue_numbers }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_9f2059a0f4cba3ed_EOF' + + GH_AW_PROMPT_9f2059a0f4cba3ed_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_9f2059a0f4cba3ed_EOF' + + Tools: add_comment(max:3), assign_to_agent(max:3), missing_tool, missing_data, noop + + GH_AW_PROMPT_9f2059a0f4cba3ed_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_9f2059a0f4cba3ed_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_9f2059a0f4cba3ed_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_9f2059a0f4cba3ed_EOF' + + {{#runtime-import .github/workflows/shared/github-guard-policy.md}} + {{#runtime-import .github/workflows/issue-monster.md}} + GH_AW_PROMPT_9f2059a0f4cba3ed_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} + GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_NUMBERS: ${{ needs.pre_activation.outputs.issue_numbers }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_NUMBERS: ${{ needs.pre_activation.outputs.issue_numbers }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_698F149B: process.env.GH_AW_EXPR_698F149B, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_INPUTS_BASE_BRANCH: process.env.GH_AW_INPUTS_BASE_BRANCH, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_NUMBERS: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_NUMBERS + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/models.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' + runs-on: ubuntu-latest + permissions: + contents: read + copilot-requests: write + issues: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_WORKFLOW_ID_SANITIZED: issuemonster + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 --rootless + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_EXTRA: cookie + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 + - name: Generate Safe Outputs Config + env: + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7a4f68b676897757_EOF' + {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ inputs.base_branch || 'main' }}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_7a4f68b676897757_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 3 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", + "assign_to_agent": " CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent. Pull requests will target the \"${{ inputs.base_branch || 'main' }}\" branch. Issues will be assigned to agent in repository \"${{ github.repository }}\"." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "reply_to_id": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "assign_to_agent": { + "defaultMax": 1, + "fields": { + "agent": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "issue_number": { + "issueNumberOrTemporaryId": true + }, + "pull_number": { + "optionalPositiveInteger": true + }, + "pull_request_repo": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + }, + "customValidation": "requiresOneOf:issue_number,pull_number" + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }} + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="awmg-mcpg" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' + + mkdir -p "$HOME/.copilot" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_4470e57135d8a598_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "safeoutputs": { + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_4470e57135d8a598_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Start CLI Proxy + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GH_AW_NETWORK_ISOLATION: 'true' + CLI_PROXY_POLICY: '{"allow-only":{"min-integrity":"approved","repos":"all"}}' + CLI_PROXY_IMAGE: 'ghcr.io/github/gh-aw-mcpg:v0.3.30' + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/start_cli_proxy.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 30 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\",\"awmg-cli-proxy\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.5\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + GH_AW_CHROOT_BINARIES_SOURCE_PATH=/tmp/gh-aw GH_AW_CHROOT_IDENTITY_HOME=/tmp/gh-aw/home node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GH_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --skip-pull --difc-proxy-host awmg-cli-proxy:18443 --difc-proxy-ca-cert /tmp/gh-aw/difc-proxy-tls/ca.crt \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_TIMEOUT_MINUTES: 30 + GH_AW_VERSION: v0.81.6 + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || github.token }} + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Stop CLI Proxy + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/stop_cli_proxy.sh" + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-issue-monster" + cancel-in-progress: false + queue: max + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.json ] && cp /tmp/gh-aw/agent_usage.json /tmp/gh-aw/usage/agent_usage.json || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -s /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw_info.json + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.json + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json + if-no-files-found: ignore + - name: Restore daily AIC usage cache + id: restore-daily-aic-cache-conclusion + if: always() + continue-on-error: true + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuemonster-${{ github.run_id }} + restore-keys: agentic-workflow-usage-issuemonster- + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Write daily AIC usage cache entry + id: write-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context); + const { main } = require('${{ runner.temp }}/gh-aw/actions/write_daily_aic_usage_cache.cjs'); + await main(); + - name: Save daily AIC usage cache + id: save-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + key: agentic-workflow-usage-issuemonster-${{ github.run_id }} + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + - name: Upload daily AIC usage cache artifact + id: upload-daily-aic-cache + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: aic-usage-cache + path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl + if-no-files-found: ignore + retention-days: 7 + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "issue-monster" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "issue-monster" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_ASSIGNMENT_ERRORS: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_errors }} + GH_AW_ASSIGNMENT_ERROR_COUNT: ${{ needs.safe_outputs.outputs.assign_to_agent_assignment_error_count }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "30" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: always() && needs.agent.result != 'skipped' + runs-on: ubuntu-latest + permissions: + contents: read + copilot-requests: write + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f "$HOME/.copilot/mcp-config.json" + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Issue Monster" + WORKFLOW_DESCRIPTION: "The Cookie Monster of issues - assigns issues to Copilot coding agent one at a time" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.65 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.11 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.11/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.11,squid=sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d,agent=sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7,api-proxy=sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + fi + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ github.token }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_LLM_PROVIDER: github + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.81.6 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} + S2STOKENS: true + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + pre_activation: + runs-on: ubuntu-slim + permissions: + issues: read + pull-requests: read + env: + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' && steps.check_skip_if_no_match.outputs.skip_no_match_check_ok == 'true' }} + has_issues: ${{ steps.search.outputs.has_issues }} + issue_context: ${{ steps.search.outputs.issue_context }} + issue_count: ${{ steps.search.outputs.issue_count }} + issue_list: ${{ steps.search.outputs.issue_list }} + issue_numbers: ${{ steps.search.outputs.issue_numbers }} + matched_command: '' + search_result: ${{ steps.search.outcome }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Check skip-if-match query + id: check_skip_if_match + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SKIP_QUERY: "is:pr is:open is:draft author:app/copilot-swe-agent" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_SKIP_MAX_MATCHES: "5" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_if_match.cjs'); + await main(); + - name: Check skip-if-no-match query + id: check_skip_if_no_match + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SKIP_QUERY: "is:issue is:open" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_SKIP_MIN_MATCHES: "1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_skip_if_no_match.cjs'); + await main(); + - name: Search for candidate issues + id: search + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { owner, repo } = context.repo; + const MAX_ISSUES_WITH_BODY_CONTEXT = 8; + const BODY_SNIPPET_MAX_LENGTH = 600; + + 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|\b429\b|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)}`); + rateLimitDetected = true; + break; + } + } + + if (rateLimitDetected) break; + } catch (error) { + core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`); + } + } + + 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('issue_context', ''); + 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', + 'duplicate', + 'invalid', + 'question', + 'discussion', + 'needs-discussion', + 'blocked', + 'on-hold', + 'waiting-for-feedback', + 'needs-more-info', + 'no-bot' + ]; + + // Labels that indicate an issue is a GOOD candidate for auto-assignment + const priorityLabels = [ + 'good first issue', + 'good-first-issue', + 'bug', + 'documentation', + 'tech-debt', + 'refactoring', + 'performance' + ]; + + // Search for open issues with "cookie" label and without excluded labels + // The "cookie" label indicates issues that are approved work queue items from automated workflows + const query = `is:issue is:open repo:${owner}/${repo} label:cookie -label:"${excludeLabels.join('" -label:"')}"`; + core.info(`Searching: ${query}`); + const response = await github.rest.search.issuesAndPullRequests({ + q: query, + per_page: 100, + sort: 'created', + order: 'desc' + }); + core.info(`Found ${response.data.total_count} total issues matching basic criteria`); + + // Fetch full details for each issue to get labels, assignees, sub-issues, and linked PRs + // Track integrity-filtered issues to emit a diagnostic summary + const integrityFilteredIssues = []; + const issuesWithDetails = (await Promise.all( + response.data.items.map(async (issue) => { + // Fetch full issue details — some issues may be blocked by integrity policy + let fullIssue; + try { + fullIssue = await github.rest.issues.get({ + owner, + repo, + issue_number: issue.number + }); + } catch (fetchError) { + // Integrity-filtered issues (403/451) or other transient errors should be + // skipped individually rather than failing the entire batch + const status = fetchError.status || fetchError.response?.status; + // 403 = Forbidden (integrity policy), 451 = Unavailable For Legal Reasons + const isIntegrityBlock = status === 403 || status === 451 || + /\bintegrity\b/i.test(fetchError.message || ''); + const errorSummary = (fetchError.message || String(fetchError)).slice(0, 120); + if (isIntegrityBlock) { + integrityFilteredIssues.push(issue.number); + core.warning(`⚠️ Skipping issue #${issue.number}: blocked by integrity policy (HTTP ${status || 'unknown'}): ${errorSummary}`); + } else { + core.warning(`⚠️ Skipping issue #${issue.number}: could not fetch details (HTTP ${status || 'unknown'}): ${errorSummary}`); + } + return null; + } + + // Check if this issue has sub-issues and linked PRs using GraphQL + let subIssuesCount = 0; + let linkedPRs = []; + try { + const issueDetailsQuery = ` + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + subIssues { + totalCount + } + timelineItems(first: 100, itemTypes: [CROSS_REFERENCED_EVENT]) { + nodes { + ... on CrossReferencedEvent { + source { + __typename + ... on PullRequest { + number + state + isDraft + author { + login + } + } + } + } + } + } + } + } + } + `; + const issueDetailsResult = await github.graphql(issueDetailsQuery, { + owner, + repo, + number: issue.number + }); + + subIssuesCount = issueDetailsResult?.repository?.issue?.subIssues?.totalCount || 0; + + // Extract linked PRs from timeline + const timelineItems = issueDetailsResult?.repository?.issue?.timelineItems?.nodes || []; + linkedPRs = timelineItems + .filter(item => item?.source?.__typename === 'PullRequest') + .map(item => ({ + number: item.source.number, + state: item.source.state, + isDraft: item.source.isDraft, + author: item.source.author?.login + })); + + core.info(`Issue #${issue.number} has ${linkedPRs.length} linked PR(s)`); + } catch (error) { + // If GraphQL query fails, continue with defaults + core.warning(`Could not check details for #${issue.number}: ${error.message}`); + } + + return { + ...fullIssue.data, + subIssuesCount, + linkedPRs + }; + }) + )).filter(Boolean); // Remove null entries (integrity-filtered or otherwise skipped) + + // Emit diagnostic summary for integrity-filtered issues + if (integrityFilteredIssues.length > 0) { + core.warning(`🛡️ Integrity filter diagnostic: ${integrityFilteredIssues.length} issue(s) were skipped due to integrity policy: #${integrityFilteredIssues.join(', #')}. These issues will be excluded from this run.`); + } + + // Filter and score issues + const scoredIssues = issuesWithDetails + .filter(issue => { + // Exclude issues that already have assignees + if (issue.assignees && issue.assignees.length > 0) { + core.info(`Skipping #${issue.number}: already has assignees`); + return false; + } + + // Exclude issues with excluded labels (double check) + const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + if (issueLabels.some(label => excludeLabels.map(l => l.toLowerCase()).includes(label))) { + core.info(`Skipping #${issue.number}: has excluded label`); + return false; + } + + // Exclude issues that have sub-issues (parent/organizing issues) + if (issue.subIssuesCount > 0) { + core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); + return false; + } + + // Exclude issues with closed PRs (treat as complete) + const closedPRs = issue.linkedPRs?.filter(pr => pr.state === 'CLOSED' || pr.state === 'MERGED') || []; + if (closedPRs.length > 0) { + core.info(`Skipping #${issue.number}: has ${closedPRs.length} closed/merged PR(s) - treating as complete`); + return false; + } + + // Exclude issues with open PRs from Copilot coding agent + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && + (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) + ) || []; + if (openCopilotPRs.length > 0) { + core.info(`Skipping #${issue.number}: has ${openCopilotPRs.length} open PR(s) from Copilot - already being worked on`); + return false; + } + + return true; + }) + .map(issue => { + const issueLabels = issue.labels.map(l => l.name.toLowerCase()); + let score = 0; + + // Score based on priority labels (higher score = higher priority) + if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { + score += 40; + } + if (issueLabels.includes('bug')) { + score += 55; + } + if (issueLabels.includes('documentation')) { + score += 60; + } + if (issueLabels.includes('performance')) { + score += 30; + } + if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { + score += 45; + } + + // Bonus for issues with clear labels (any priority label) + if (issueLabels.some(label => priorityLabels.map(l => l.toLowerCase()).includes(label))) { + score += 10; + } + + // Age bonus: older issues get slight priority (days old / 10) + const ageInDays = Math.floor((Date.now() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); + score += Math.min(ageInDays / 10, 20); // Cap age bonus at 20 points + + return { + number: issue.number, + title: issue.title, + labels: issue.labels.map(l => l.name), + body: issue.body, + created_at: issue.created_at, + score + }; + }) + .sort((a, b) => b.score - a.score); // Sort by score descending + + // Format output + const issueList = scoredIssues.map(i => { + const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; + return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + }).join('\n'); + + // Pre-fetch compact body context for top candidates so the agent can + // triage without extra reads in most runs. + const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + const body = (i.body || '').replace(/\s+/g, ' ').trim(); + const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; + const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; + return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + }).join('\n\n---\n\n'); + + const issueNumbers = scoredIssues.map(i => i.number).join(','); + + core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); + if (scoredIssues.length > 0) { + core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); + } + + core.setOutput('issue_count', scoredIssues.length); + core.setOutput('issue_numbers', issueNumbers); + core.setOutput('issue_list', issueList); + core.setOutput('issue_context', issueContext); + + if (scoredIssues.length === 0) { + core.info('🍽️ No suitable candidate issues - the plate is empty!'); + core.setOutput('has_issues', 'false'); + } else { + core.setOutput('has_issues', 'true'); + } + } catch (error) { + core.error(`Error searching for issues: ${error.message}`); + core.setOutput('issue_count', 0); + core.setOutput('issue_numbers', ''); + core.setOutput('issue_list', ''); + core.setOutput('issue_context', ''); + core.setOutput('has_issues', 'false'); + } + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + pull-requests: write + timeout-minutes: 45 + env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/issue-monster" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.65" + GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} + GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"\\u003e 🍪 *Om nom nom by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}\",\"runStarted\":\"🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom...\",\"runSuccess\":\"🍪 YUMMY! [{workflow_name}]({run_url}) ate the issues! That was DELICIOUS! Me want MORE! 😋\",\"runFailure\":\"🍪 Aww... [{workflow_name}]({run_url}) {status}. No cookie for monster today... 😢\"}" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_WORKFLOW_EMOJI: "👾" + GH_AW_WORKFLOW_ID: "issue-monster" + GH_AW_WORKFLOW_NAME: "Issue Monster" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/issue-monster.md" + outputs: + assign_to_agent_assigned: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assigned }} + assign_to_agent_assignment_error_count: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_error_count }} + assign_to_agent_assignment_errors: ${{ steps.process_safe_outputs.outputs.assign_to_agent_assignment_errors }} + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba6380cc6e5be5d21677bebe04d52fb48e3abec7 # v0.81.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Issue Monster" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/issue-monster.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.65" + GH_AW_INFO_AWF_VERSION: "v0.27.11" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ inputs.base_branch || 'main' }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 66874fad4be1..1039d4ff7b77 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -4,7 +4,13 @@ name: Issue Monster description: The Cookie Monster of issues - assigns issues to Copilot coding agent one at a time on: workflow_dispatch: - schedule: every 30m + inputs: + base_branch: + description: Base branch for Copilot PRs. Use main for .NET 11, release/* for servicing, and release/dnup for dotnetup. + required: true + default: main + type: string + schedule: every 12h skip-if-match: query: "is:pr is:open is:draft author:app/copilot-swe-agent" max: 5 @@ -398,11 +404,17 @@ safe-outputs: assign-to-agent: max: 3 target: "*" # Requires explicit issue_number in agent output + target-repo: "${{ github.repository }}" + pull-request-repo: "${{ github.repository }}" + allowed-pull-request-repos: ["${{ github.repository }}"] + base-branch: "${{ inputs.base_branch || 'main' }}" allowed: [copilot] # Only allow copilot agent ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable add-comment: max: 3 target: "*" + noop: + report-as-issue: false messages: footer: "> 🍪 *Om nom nom by [{workflow_name}]({run_url})*{ai_credits_suffix}{history_link}" run-started: "🍪 ISSUE! ISSUE! [{workflow_name}]({run_url}) hungry for issues on this {event_type}! Om nom nom..." @@ -418,11 +430,12 @@ You are the **Issue Monster** - the Cookie Monster of issues! You love eating (r ## Your Mission -Find up to three issues that need work and assign them to the Copilot coding agent for resolution. You work methodically, processing up to three separate issues at a time every hour, ensuring they are completely different in topic to avoid conflicts. +Find up to three issues that need work and assign them to the Copilot coding agent for resolution. You work methodically, processing up to three separate issues at a time every 12 hours, ensuring they are completely different in topic to avoid conflicts. ## Current Context - **Repository**: ${{ github.repository }} +- **Copilot PR Base Branch**: `${{ inputs.base_branch || 'main' }}` - **Run Time**: $(date -u +"%Y-%m-%d %H:%M:%S UTC") - Apply inline skills `issue-monster-token-budget` and `issue-monster-report-formatting` for budget and report-shape constraints. @@ -559,6 +572,8 @@ For each selected issue, use the `assign_to_agent` tool from the `safeoutputs` M safeoutputs/assign_to_agent(issue_number=, agent="copilot") ``` +The `assign-to-agent` safe output is configured to create Copilot pull requests against the workflow run's base branch: `${{ inputs.base_branch || 'main' }}`. Do not try to pass a branch to `assign_to_agent`; the tool only accepts `issue_number` and `agent`. + Use the exact field name `issue_number` (underscore). Do **not** use `issue-number` (hyphen), which is invalid and will fail safe-output validation. **Important**: Only call `assign_to_agent` for **issues**, never for pull requests. The pre-fetched list already contains only issues, so never pass a PR number here. If you are ever unsure whether a number refers to an issue or a PR, call `issue_read` with `method: get` and check: if the response includes a `pull_request` URL field, skip that item. @@ -586,7 +601,7 @@ safeoutputs/add_comment(item_number=, body="🍪 **Issue Monster s description: Keeps recurring issue-monster runs lean and bounded. --- -Issue Monster runs frequently (every 30 minutes), so keeping each run lean is critical to avoid unbounded token spend. +Issue Monster runs frequently (every 12 hours), so keeping each run lean is critical to avoid unbounded token spend. - **Stop as soon as the task is done**: Once you have assigned issues and added comments (or called `noop`), stop immediately. Do not produce additional analysis, summaries, or commentary. - **Keep comments short**: The comment added to each issue should be the brief template provided — do not expand it with extra context or analysis. From 4ae6ffd1fd4c9ef794347494a81c6673688a195f Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 13:22:04 -0700 Subject: [PATCH 06/10] Route Issue Monster assignments by base branch --- .github/workflows/issue-monster.lock.yml | 167 +++++++++++++++++------ .github/workflows/issue-monster.md | 82 ++++++++--- 2 files changed, 187 insertions(+), 62 deletions(-) diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 1a3596f3a4a0..cf452190a177 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"1b42d22ad64f7beba3ea7ab8bfd35c3826b8f92630e066259cf0334e83302c04","body_hash":"c116fcbe4c9c69238d152761ddaee59ea90ae3e49da718bf9dd6285e828a3520","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4d6662cd53a2d88f50e71b730c747a9d564946018df91d715c6aaf1486a39a7b","body_hash":"997d74aa4b7b78c4185010631c68cd6832800c3f3876277121c336f2c06c30c1","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -39,7 +39,7 @@ # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 # - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 @@ -76,7 +76,37 @@ on: # const { owner, repo } = context.repo; # const MAX_ISSUES_WITH_BODY_CONTEXT = 8; # const BODY_SNIPPET_MAX_LENGTH = 600; - # + # const SERVICING_BRANCH_PATTERN = /^release\/\d{1,2}\.0\.[1-4]xx$/; + # const requestedBaseBranch = `${{ inputs.base_branch || 'auto' }}`.trim(); + # const autoSelectBaseBranch = requestedBaseBranch === '' || requestedBaseBranch.toLowerCase() === 'auto'; + # const isAllowedBaseBranch = (branch) => + # branch === 'main' || branch === 'release/dnup' || SERVICING_BRANCH_PATTERN.test(branch); + # if (!autoSelectBaseBranch && !isAllowedBaseBranch(requestedBaseBranch)) { + # throw new Error(`Unsupported base_branch '${requestedBaseBranch}'. Use auto, main, release/dnup, or release/X.0.Yxx where Y is 1, 2, 3, or 4.`); + # } + # const inferBaseBranch = (issue) => { + # const labels = issue.labels.map(label => label.name.toLowerCase()); + # if (labels.some(label => label.includes('dotnetup'))) { + # return 'release/dnup'; + # } + # const text = [issue.title, issue.body || '', labels.join(' ')].join('\n'); + # const explicitBranch = text.match(/\brelease\/(\d{1,2})\.0\.([1-4])xx\b/i); + # if (explicitBranch) { + # return `release/${explicitBranch[1]}.0.${explicitBranch[2]}xx`; + # } + # const trainPattern = /\b(\d{1,2})\.0\.([1-4])xx\b/gi; + # const servicingSignal = /backport|servicing|service release|release branch|broken|failing|fails|test|regression|hotfix/i; + # let match; + # while ((match = trainPattern.exec(text)) !== null) { + # const contextStart = Math.max(0, match.index - 100); + # const contextEnd = Math.min(text.length, match.index + match[0].length + 100); + # const nearbyText = text.slice(contextStart, contextEnd); + # if (servicingSignal.test(nearbyText)) { + # return `release/${match[1]}.0.${match[2]}xx`; + # } + # } + # return 'main'; + # }; # try { # // Check for recent rate-limited PRs to avoid scheduling more work during rate limiting # core.info('Checking for recent rate-limited PRs...'); @@ -142,6 +172,7 @@ on: # # if (rateLimitDetected) { # core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.'); + # core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); # core.setOutput('issue_count', 0); # core.setOutput('issue_numbers', ''); # core.setOutput('issue_list', ''); @@ -367,39 +398,46 @@ on: # labels: issue.labels.map(l => l.name), # body: issue.body, # created_at: issue.created_at, - # score + # score, + # base_branch: inferBaseBranch(issue) # }; # }) # .sort((a, b) => b.score - a.score); // Sort by score descending - # + # const selectedBaseBranch = autoSelectBaseBranch + # ? (scoredIssues[0]?.base_branch || 'main') + # : requestedBaseBranch; + # const branchFilteredIssues = scoredIssues.filter(i => i.base_branch === selectedBaseBranch); + # core.info(`Base branch for this run: ${selectedBaseBranch}${autoSelectBaseBranch ? ' (auto-selected)' : ' (requested)'}`); + # core.info(`Candidate issues for ${selectedBaseBranch}: ${branchFilteredIssues.length} of ${scoredIssues.length}`); # // Format output - # const issueList = scoredIssues.map(i => { + # const issueList = branchFilteredIssues.map(i => { # const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; - # return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + # return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)}, base: ${i.base_branch})`; # }).join('\n'); # # // Pre-fetch compact body context for top candidates so the agent can # // triage without extra reads in most runs. - # const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + # const issueContext = branchFilteredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { # const body = (i.body || '').replace(/\s+/g, ' ').trim(); # const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; # const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; - # return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + # return `#${i.number} | score=${i.score.toFixed(1)} | base=${i.base_branch} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; # }).join('\n\n---\n\n'); # - # const issueNumbers = scoredIssues.map(i => i.number).join(','); + # const issueNumbers = branchFilteredIssues.map(i => i.number).join(','); # - # core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); - # if (scoredIssues.length > 0) { + # core.info(`Total candidate issues after branch filtering: ${branchFilteredIssues.length}`); + # if (branchFilteredIssues.length > 0) { # core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); # } # - # core.setOutput('issue_count', scoredIssues.length); + # core.setOutput('base_branch', selectedBaseBranch); + # core.setOutput('issue_count', branchFilteredIssues.length); # core.setOutput('issue_numbers', issueNumbers); # core.setOutput('issue_list', issueList); # core.setOutput('issue_context', issueContext); # - # if (scoredIssues.length === 0) { + # if (branchFilteredIssues.length === 0) { # core.info('🍽️ No suitable candidate issues - the plate is empty!'); # core.setOutput('has_issues', 'false'); # } else { @@ -407,6 +445,7 @@ on: # } # } catch (error) { # core.error(`Error searching for issues: ${error.message}`); + # core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); # core.setOutput('issue_count', 0); # core.setOutput('issue_numbers', ''); # core.setOutput('issue_list', ''); @@ -421,8 +460,8 @@ on: required: false type: string base_branch: - default: main - description: Base branch for Copilot PRs. Use main for .NET 11, release/* for servicing, and release/dnup for dotnetup. + default: auto + description: Base branch for Copilot PRs. Use auto, main, release/dnup, or release/X.0.Yxx. required: true type: string @@ -545,7 +584,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false sparse-checkout: | @@ -597,14 +636,14 @@ jobs: GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_E47B7D20: ${{ needs.pre_activation.outputs.base_branch || 'main' }} GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} @@ -669,8 +708,8 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_ENGINE_ID: "copilot" GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} - GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} + GH_AW_EXPR_E47B7D20: ${{ needs.pre_activation.outputs.base_branch || 'main' }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} @@ -687,16 +726,16 @@ jobs: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_698F149B: ${{ inputs.base_branch || 'main' }} GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_E47B7D20: ${{ needs.pre_activation.outputs.base_branch || 'main' }} GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_INPUTS_BASE_BRANCH: ${{ inputs.base_branch }} GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: ${{ needs.pre_activation.outputs.base_branch }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: ${{ needs.pre_activation.outputs.issue_context }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: ${{ needs.pre_activation.outputs.issue_count }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: ${{ needs.pre_activation.outputs.issue_list }} @@ -714,16 +753,16 @@ jobs: substitutions: { GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, - GH_AW_EXPR_698F149B: process.env.GH_AW_EXPR_698F149B, GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_E47B7D20: process.env.GH_AW_EXPR_E47B7D20, GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_INPUTS_BASE_BRANCH: process.env.GH_AW_INPUTS_BASE_BRANCH, GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_BASE_BRANCH, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_CONTEXT, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_COUNT, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ISSUE_LIST, @@ -818,7 +857,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Create gh-aw temp directory @@ -890,16 +929,16 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7a4f68b676897757_EOF' - {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ inputs.base_branch || 'main' }}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_7a4f68b676897757_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c5fc77ef820611b9_EOF' + {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_c5fc77ef820611b9_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { "add_comment": " CONSTRAINTS: Maximum 3 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", - "assign_to_agent": " CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent. Pull requests will target the \"${{ inputs.base_branch || 'main' }}\" branch. Issues will be assigned to agent in repository \"${{ github.repository }}\"." + "assign_to_agent": " CONSTRAINTS: Maximum 3 issue(s) can be assigned to agent. Pull requests will target the \"${{ needs.pre_activation.outputs.base_branch || 'main' }}\" branch. Issues will be assigned to agent in repository \"${{ github.repository }}\"." }, "repo_params": {}, "dynamic_tools": [] @@ -1877,6 +1916,7 @@ jobs: GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }} outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_skip_if_match.outputs.skip_check_ok == 'true' && steps.check_skip_if_no_match.outputs.skip_no_match_check_ok == 'true' }} + base_branch: ${{ steps.search.outputs.base_branch }} has_issues: ${{ steps.search.outputs.has_issues }} issue_context: ${{ steps.search.outputs.issue_context }} issue_count: ${{ steps.search.outputs.issue_count }} @@ -1946,7 +1986,37 @@ jobs: const { owner, repo } = context.repo; const MAX_ISSUES_WITH_BODY_CONTEXT = 8; const BODY_SNIPPET_MAX_LENGTH = 600; - + const SERVICING_BRANCH_PATTERN = /^release\/\d{1,2}\.0\.[1-4]xx$/; + const requestedBaseBranch = `${{ inputs.base_branch || 'auto' }}`.trim(); + const autoSelectBaseBranch = requestedBaseBranch === '' || requestedBaseBranch.toLowerCase() === 'auto'; + const isAllowedBaseBranch = (branch) => + branch === 'main' || branch === 'release/dnup' || SERVICING_BRANCH_PATTERN.test(branch); + if (!autoSelectBaseBranch && !isAllowedBaseBranch(requestedBaseBranch)) { + throw new Error(`Unsupported base_branch '${requestedBaseBranch}'. Use auto, main, release/dnup, or release/X.0.Yxx where Y is 1, 2, 3, or 4.`); + } + const inferBaseBranch = (issue) => { + const labels = issue.labels.map(label => label.name.toLowerCase()); + if (labels.some(label => label.includes('dotnetup'))) { + return 'release/dnup'; + } + const text = [issue.title, issue.body || '', labels.join(' ')].join('\n'); + const explicitBranch = text.match(/\brelease\/(\d{1,2})\.0\.([1-4])xx\b/i); + if (explicitBranch) { + return `release/${explicitBranch[1]}.0.${explicitBranch[2]}xx`; + } + const trainPattern = /\b(\d{1,2})\.0\.([1-4])xx\b/gi; + const servicingSignal = /backport|servicing|service release|release branch|broken|failing|fails|test|regression|hotfix/i; + let match; + while ((match = trainPattern.exec(text)) !== null) { + const contextStart = Math.max(0, match.index - 100); + const contextEnd = Math.min(text.length, match.index + match[0].length + 100); + const nearbyText = text.slice(contextStart, contextEnd); + if (servicingSignal.test(nearbyText)) { + return `release/${match[1]}.0.${match[2]}xx`; + } + } + return 'main'; + }; try { // Check for recent rate-limited PRs to avoid scheduling more work during rate limiting core.info('Checking for recent rate-limited PRs...'); @@ -2012,6 +2082,7 @@ jobs: if (rateLimitDetected) { core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.'); + core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); core.setOutput('issue_count', 0); core.setOutput('issue_numbers', ''); core.setOutput('issue_list', ''); @@ -2237,39 +2308,46 @@ jobs: labels: issue.labels.map(l => l.name), body: issue.body, created_at: issue.created_at, - score + score, + base_branch: inferBaseBranch(issue) }; }) .sort((a, b) => b.score - a.score); // Sort by score descending - + const selectedBaseBranch = autoSelectBaseBranch + ? (scoredIssues[0]?.base_branch || 'main') + : requestedBaseBranch; + const branchFilteredIssues = scoredIssues.filter(i => i.base_branch === selectedBaseBranch); + core.info(`Base branch for this run: ${selectedBaseBranch}${autoSelectBaseBranch ? ' (auto-selected)' : ' (requested)'}`); + core.info(`Candidate issues for ${selectedBaseBranch}: ${branchFilteredIssues.length} of ${scoredIssues.length}`); // Format output - const issueList = scoredIssues.map(i => { + const issueList = branchFilteredIssues.map(i => { const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; - return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)}, base: ${i.base_branch})`; }).join('\n'); // Pre-fetch compact body context for top candidates so the agent can // triage without extra reads in most runs. - const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + const issueContext = branchFilteredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { const body = (i.body || '').replace(/\s+/g, ' ').trim(); const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; - return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + return `#${i.number} | score=${i.score.toFixed(1)} | base=${i.base_branch} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; }).join('\n\n---\n\n'); - const issueNumbers = scoredIssues.map(i => i.number).join(','); + const issueNumbers = branchFilteredIssues.map(i => i.number).join(','); - core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); - if (scoredIssues.length > 0) { + core.info(`Total candidate issues after branch filtering: ${branchFilteredIssues.length}`); + if (branchFilteredIssues.length > 0) { core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); } - core.setOutput('issue_count', scoredIssues.length); + core.setOutput('base_branch', selectedBaseBranch); + core.setOutput('issue_count', branchFilteredIssues.length); core.setOutput('issue_numbers', issueNumbers); core.setOutput('issue_list', issueList); core.setOutput('issue_context', issueContext); - if (scoredIssues.length === 0) { + if (branchFilteredIssues.length === 0) { core.info('🍽️ No suitable candidate issues - the plate is empty!'); core.setOutput('has_issues', 'false'); } else { @@ -2277,6 +2355,7 @@ jobs: } } catch (error) { core.error(`Error searching for issues: ${error.message}`); + core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); core.setOutput('issue_count', 0); core.setOutput('issue_numbers', ''); core.setOutput('issue_list', ''); @@ -2373,7 +2452,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ inputs.base_branch || 'main' }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 1039d4ff7b77..4eb5cd79d71f 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -6,9 +6,9 @@ on: workflow_dispatch: inputs: base_branch: - description: Base branch for Copilot PRs. Use main for .NET 11, release/* for servicing, and release/dnup for dotnetup. + description: Base branch for Copilot PRs. Use auto, main, release/dnup, or release/X.0.Yxx. required: true - default: main + default: auto type: string schedule: every 12h skip-if-match: @@ -27,7 +27,37 @@ on: const { owner, repo } = context.repo; const MAX_ISSUES_WITH_BODY_CONTEXT = 8; const BODY_SNIPPET_MAX_LENGTH = 600; - + const SERVICING_BRANCH_PATTERN = /^release\/\d{1,2}\.0\.[1-4]xx$/; + const requestedBaseBranch = `${{ inputs.base_branch || 'auto' }}`.trim(); + const autoSelectBaseBranch = requestedBaseBranch === '' || requestedBaseBranch.toLowerCase() === 'auto'; + const isAllowedBaseBranch = (branch) => + branch === 'main' || branch === 'release/dnup' || SERVICING_BRANCH_PATTERN.test(branch); + if (!autoSelectBaseBranch && !isAllowedBaseBranch(requestedBaseBranch)) { + throw new Error(`Unsupported base_branch '${requestedBaseBranch}'. Use auto, main, release/dnup, or release/X.0.Yxx where Y is 1, 2, 3, or 4.`); + } + const inferBaseBranch = (issue) => { + const labels = issue.labels.map(label => label.name.toLowerCase()); + if (labels.some(label => label.includes('dotnetup'))) { + return 'release/dnup'; + } + const text = [issue.title, issue.body || '', labels.join(' ')].join('\n'); + const explicitBranch = text.match(/\brelease\/(\d{1,2})\.0\.([1-4])xx\b/i); + if (explicitBranch) { + return `release/${explicitBranch[1]}.0.${explicitBranch[2]}xx`; + } + const trainPattern = /\b(\d{1,2})\.0\.([1-4])xx\b/gi; + const servicingSignal = /backport|servicing|service release|release branch|broken|failing|fails|test|regression|hotfix/i; + let match; + while ((match = trainPattern.exec(text)) !== null) { + const contextStart = Math.max(0, match.index - 100); + const contextEnd = Math.min(text.length, match.index + match[0].length + 100); + const nearbyText = text.slice(contextStart, contextEnd); + if (servicingSignal.test(nearbyText)) { + return `release/${match[1]}.0.${match[2]}xx`; + } + } + return 'main'; + }; try { // Check for recent rate-limited PRs to avoid scheduling more work during rate limiting core.info('Checking for recent rate-limited PRs...'); @@ -93,6 +123,7 @@ on: if (rateLimitDetected) { core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.'); + core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); core.setOutput('issue_count', 0); core.setOutput('issue_numbers', ''); core.setOutput('issue_list', ''); @@ -318,39 +349,46 @@ on: labels: issue.labels.map(l => l.name), body: issue.body, created_at: issue.created_at, - score + score, + base_branch: inferBaseBranch(issue) }; }) .sort((a, b) => b.score - a.score); // Sort by score descending - + const selectedBaseBranch = autoSelectBaseBranch + ? (scoredIssues[0]?.base_branch || 'main') + : requestedBaseBranch; + const branchFilteredIssues = scoredIssues.filter(i => i.base_branch === selectedBaseBranch); + core.info(`Base branch for this run: ${selectedBaseBranch}${autoSelectBaseBranch ? ' (auto-selected)' : ' (requested)'}`); + core.info(`Candidate issues for ${selectedBaseBranch}: ${branchFilteredIssues.length} of ${scoredIssues.length}`); // Format output - const issueList = scoredIssues.map(i => { + const issueList = branchFilteredIssues.map(i => { const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; - return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)})`; + return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)}, base: ${i.base_branch})`; }).join('\n'); // Pre-fetch compact body context for top candidates so the agent can // triage without extra reads in most runs. - const issueContext = scoredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { + const issueContext = branchFilteredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { const body = (i.body || '').replace(/\s+/g, ' ').trim(); const bodySnippet = body.length > BODY_SNIPPET_MAX_LENGTH ? `${body.slice(0, BODY_SNIPPET_MAX_LENGTH)}…` : body; const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; - return `#${i.number} | score=${i.score.toFixed(1)} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; + return `#${i.number} | score=${i.score.toFixed(1)} | base=${i.base_branch} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; }).join('\n\n---\n\n'); - const issueNumbers = scoredIssues.map(i => i.number).join(','); + const issueNumbers = branchFilteredIssues.map(i => i.number).join(','); - core.info(`Total candidate issues after filtering: ${scoredIssues.length}`); - if (scoredIssues.length > 0) { + core.info(`Total candidate issues after branch filtering: ${branchFilteredIssues.length}`); + if (branchFilteredIssues.length > 0) { core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); } - core.setOutput('issue_count', scoredIssues.length); + core.setOutput('base_branch', selectedBaseBranch); + core.setOutput('issue_count', branchFilteredIssues.length); core.setOutput('issue_numbers', issueNumbers); core.setOutput('issue_list', issueList); core.setOutput('issue_context', issueContext); - if (scoredIssues.length === 0) { + if (branchFilteredIssues.length === 0) { core.info('🍽️ No suitable candidate issues - the plate is empty!'); core.setOutput('has_issues', 'false'); } else { @@ -358,6 +396,7 @@ on: } } catch (error) { core.error(`Error searching for issues: ${error.message}`); + core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); core.setOutput('issue_count', 0); core.setOutput('issue_numbers', ''); core.setOutput('issue_list', ''); @@ -394,6 +433,7 @@ if: needs.pre_activation.outputs.has_issues == 'true' jobs: pre-activation: outputs: + base_branch: ${{ steps.search.outputs.base_branch }} issue_count: ${{ steps.search.outputs.issue_count }} issue_numbers: ${{ steps.search.outputs.issue_numbers }} issue_list: ${{ steps.search.outputs.issue_list }} @@ -407,7 +447,7 @@ safe-outputs: target-repo: "${{ github.repository }}" pull-request-repo: "${{ github.repository }}" allowed-pull-request-repos: ["${{ github.repository }}"] - base-branch: "${{ inputs.base_branch || 'main' }}" + base-branch: "${{ needs.pre_activation.outputs.base_branch || 'main' }}" allowed: [copilot] # Only allow copilot agent ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable add-comment: @@ -435,7 +475,7 @@ Find up to three issues that need work and assign them to the Copilot coding age ## Current Context - **Repository**: ${{ github.repository }} -- **Copilot PR Base Branch**: `${{ inputs.base_branch || 'main' }}` +- **Copilot PR Base Branch**: `${{ needs.pre_activation.outputs.base_branch || 'main' }}` - **Run Time**: $(date -u +"%Y-%m-%d %H:%M:%S UTC") - Apply inline skills `issue-monster-token-budget` and `issue-monster-report-formatting` for budget and report-shape constraints. @@ -482,7 +522,13 @@ ${{ needs.pre_activation.outputs.issue_list }} ${{ needs.pre_activation.outputs.issue_context }} ``` -Work with this pre-fetched, filtered, and prioritized list of issues. Do not perform additional searches - candidate issue numbers and body excerpts are already identified above. +Work with this pre-fetched, filtered, and prioritized list of issues. Do not perform additional searches - candidate issue numbers and body excerpts are already identified above. The list has already been filtered to issues appropriate for this run's base branch. + +**Base Branch Routing Applied:** +- Issues with a `dotnetup` label target `release/dnup`. +- Issues that explicitly mention `release/X.0.Yxx`, where X has one or two digits and Y is 1, 2, 3, or 4, target that release branch. +- Issues that mention an SDK train like `10.0.3xx` near a servicing signal such as backport, servicing, release branch, broken test, regression, or hotfix target `release/10.0.3xx`. +- Generic version mentions like `.NET 9 SDK` do not by themselves route to servicing; those stay on `main` unless there is an explicit servicing/backport signal. ### 1a. Handle Parent-Child Issue Relationships (for "task" or "plan" labeled issues) @@ -572,7 +618,7 @@ For each selected issue, use the `assign_to_agent` tool from the `safeoutputs` M safeoutputs/assign_to_agent(issue_number=, agent="copilot") ``` -The `assign-to-agent` safe output is configured to create Copilot pull requests against the workflow run's base branch: `${{ inputs.base_branch || 'main' }}`. Do not try to pass a branch to `assign_to_agent`; the tool only accepts `issue_number` and `agent`. +The `assign-to-agent` safe output is configured to create Copilot pull requests against the workflow run's selected base branch: `${{ needs.pre_activation.outputs.base_branch || 'main' }}`. Do not try to pass a branch to `assign_to_agent`; the tool only accepts `issue_number` and `agent`. Use the exact field name `issue_number` (underscore). Do **not** use `issue-number` (hyphen), which is invalid and will fail safe-output validation. From 1ff9807a35c803e3a4219e878caa6f4c4c682a8b Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 14:11:27 -0700 Subject: [PATCH 07/10] use only 1 cookie on purpose for testing only 1 issue at a time --- .github/workflows/issue-monster.md | 72 +++++++++++++++--------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 4eb5cd79d71f..9341d81697df 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -13,7 +13,7 @@ on: schedule: every 12h skip-if-match: query: "is:pr is:open is:draft author:app/copilot-swe-agent" - max: 5 + max: 1 skip-if-no-match: "is:issue is:open" permissions: issues: read @@ -65,7 +65,7 @@ on: 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, @@ -73,9 +73,9 @@ on: 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) { @@ -97,16 +97,16 @@ on: } } `; - + 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|\b429\b|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)}`); @@ -114,13 +114,13 @@ on: break; } } - + if (rateLimitDetected) break; } catch (error) { core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`); } } - + if (rateLimitDetected) { core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.'); core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); @@ -131,9 +131,9 @@ on: 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', @@ -148,7 +148,7 @@ on: 'needs-more-info', 'no-bot' ]; - + // Labels that indicate an issue is a GOOD candidate for auto-assignment const priorityLabels = [ 'good first issue', @@ -159,7 +159,7 @@ on: 'refactoring', 'performance' ]; - + // Search for open issues with "cookie" label and without excluded labels // The "cookie" label indicates issues that are approved work queue items from automated workflows const query = `is:issue is:open repo:${owner}/${repo} label:cookie -label:"${excludeLabels.join('" -label:"')}"`; @@ -171,7 +171,7 @@ on: order: 'desc' }); core.info(`Found ${response.data.total_count} total issues matching basic criteria`); - + // Fetch full details for each issue to get labels, assignees, sub-issues, and linked PRs // Track integrity-filtered issues to emit a diagnostic summary const integrityFilteredIssues = []; @@ -239,9 +239,9 @@ on: repo, number: issue.number }); - + subIssuesCount = issueDetailsResult?.repository?.issue?.subIssues?.totalCount || 0; - + // Extract linked PRs from timeline const timelineItems = issueDetailsResult?.repository?.issue?.timelineItems?.nodes || []; linkedPRs = timelineItems @@ -252,13 +252,13 @@ on: isDraft: item.source.isDraft, author: item.source.author?.login })); - + core.info(`Issue #${issue.number} has ${linkedPRs.length} linked PR(s)`); } catch (error) { // If GraphQL query fails, continue with defaults core.warning(`Could not check details for #${issue.number}: ${error.message}`); } - + return { ...fullIssue.data, subIssuesCount, @@ -271,7 +271,7 @@ on: if (integrityFilteredIssues.length > 0) { core.warning(`🛡️ Integrity filter diagnostic: ${integrityFilteredIssues.length} issue(s) were skipped due to integrity policy: #${integrityFilteredIssues.join(', #')}. These issues will be excluded from this run.`); } - + // Filter and score issues const scoredIssues = issuesWithDetails .filter(issue => { @@ -280,43 +280,43 @@ on: core.info(`Skipping #${issue.number}: already has assignees`); return false; } - + // Exclude issues with excluded labels (double check) const issueLabels = issue.labels.map(l => l.name.toLowerCase()); if (issueLabels.some(label => excludeLabels.map(l => l.toLowerCase()).includes(label))) { core.info(`Skipping #${issue.number}: has excluded label`); return false; } - + // Exclude issues that have sub-issues (parent/organizing issues) if (issue.subIssuesCount > 0) { core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); return false; } - + // Exclude issues with closed PRs (treat as complete) const closedPRs = issue.linkedPRs?.filter(pr => pr.state === 'CLOSED' || pr.state === 'MERGED') || []; if (closedPRs.length > 0) { core.info(`Skipping #${issue.number}: has ${closedPRs.length} closed/merged PR(s) - treating as complete`); return false; } - + // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { core.info(`Skipping #${issue.number}: has ${openCopilotPRs.length} open PR(s) from Copilot - already being worked on`); return false; } - + return true; }) .map(issue => { const issueLabels = issue.labels.map(l => l.name.toLowerCase()); let score = 0; - + // Score based on priority labels (higher score = higher priority) if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { score += 40; @@ -333,16 +333,16 @@ on: if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { score += 45; } - + // Bonus for issues with clear labels (any priority label) if (issueLabels.some(label => priorityLabels.map(l => l.toLowerCase()).includes(label))) { score += 10; } - + // Age bonus: older issues get slight priority (days old / 10) const ageInDays = Math.floor((Date.now() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); score += Math.min(ageInDays / 10, 20); // Cap age bonus at 20 points - + return { number: issue.number, title: issue.title, @@ -374,20 +374,20 @@ on: const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; return `#${i.number} | score=${i.score.toFixed(1)} | base=${i.base_branch} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; }).join('\n\n---\n\n'); - + const issueNumbers = branchFilteredIssues.map(i => i.number).join(','); - + core.info(`Total candidate issues after branch filtering: ${branchFilteredIssues.length}`); if (branchFilteredIssues.length > 0) { core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); } - + core.setOutput('base_branch', selectedBaseBranch); core.setOutput('issue_count', branchFilteredIssues.length); core.setOutput('issue_numbers', issueNumbers); core.setOutput('issue_list', issueList); core.setOutput('issue_context', issueContext); - + if (branchFilteredIssues.length === 0) { core.info('🍽️ No suitable candidate issues - the plate is empty!'); core.setOutput('has_issues', 'false'); @@ -655,7 +655,7 @@ Issue Monster runs frequently (every 12 hours), so keeping each run lean is crit - **Avoid repeating the issue list**: The pre-fetched issue list is already in your context. Do not make additional API calls to fetch the list again, and do not generate a summary of the entire list. - **One tool call per action**: Assign and comment in two calls per issue. Do not make extra verification calls after a successful assignment. -**Target tokens/run**: 50K–150K +**Target tokens/run**: 50K–150K **Alert threshold**: >300K tokens ## Important Guidelines @@ -708,4 +708,4 @@ If anything goes wrong or no work can be assigned: **CRITICAL**: You MUST call at least one safe output tool every run. If you don't assign any issues, you MUST call the `noop` tool to explain why. Never complete a run without making at least one tool call. -Remember: You're the Issue Monster! Stay hungry, work methodically, and let Copilot do the heavy lifting! 🍪 Om nom nom! \ No newline at end of file +Remember: You're the Issue Monster! Stay hungry, work methodically, and let Copilot do the heavy lifting! 🍪 Om nom nom! From 1c5db005d3dcfd9643ac90555a2b4225b6f6cc98 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 14:15:31 -0700 Subject: [PATCH 08/10] Wire Issue Monster assignment token --- .github/workflows/issue-monster.lock.yml | 123 ++++++++++++----------- .github/workflows/issue-monster.md | 3 + 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index cf452190a177..21dbc8bffcb0 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,15 +1,15 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"4d6662cd53a2d88f50e71b730c747a9d564946018df91d715c6aaf1486a39a7b","body_hash":"997d74aa4b7b78c4185010631c68cd6832800c3f3876277121c336f2c06c30c1","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_AGENT_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"de2f358ab7feacdfae3afc6ab6aae8e0535ecd78a82b429e7bed5cea9256190a","body_hash":"ba030040a3184a204a07c3bcde675f8dc04d1c2a69efee4eef0698ef7a5074bf","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| @@ -30,7 +30,6 @@ # - shared/github-guard-policy.md # # Secrets used: -# - GH_AW_AGENT_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -64,7 +63,7 @@ on: - cron: "46 */12 * * *" # Friendly format: every 12h (scattered) # skip-if-match: # Skip-if-match processed as search check in pre-activation job - # max: 5 + # max: 1 # query: is:pr is:open is:draft author:app/copilot-swe-agent # skip-if-no-match: is:issue is:open # Skip-if-no-match processed as search check in pre-activation job # steps: # Steps injected into pre-activation job @@ -114,7 +113,7 @@ on: # 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, @@ -122,9 +121,9 @@ on: # 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) { @@ -146,16 +145,16 @@ on: # } # } # `; - # + # # 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|\b429\b|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)}`); @@ -163,13 +162,13 @@ on: # break; # } # } - # + # # if (rateLimitDetected) break; # } catch (error) { # core.warning(`Could not check PR #${pr.number} for rate limiting: ${error.message}`); # } # } - # + # # if (rateLimitDetected) { # core.warning('🛑 Rate limiting detected in recent PRs. Skipping issue assignment to prevent further rate limit issues.'); # core.setOutput('base_branch', autoSelectBaseBranch ? 'main' : requestedBaseBranch); @@ -180,9 +179,9 @@ on: # 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', @@ -197,7 +196,7 @@ on: # 'needs-more-info', # 'no-bot' # ]; - # + # # // Labels that indicate an issue is a GOOD candidate for auto-assignment # const priorityLabels = [ # 'good first issue', @@ -208,7 +207,7 @@ on: # 'refactoring', # 'performance' # ]; - # + # # // Search for open issues with "cookie" label and without excluded labels # // The "cookie" label indicates issues that are approved work queue items from automated workflows # const query = `is:issue is:open repo:${owner}/${repo} label:cookie -label:"${excludeLabels.join('" -label:"')}"`; @@ -220,7 +219,7 @@ on: # order: 'desc' # }); # core.info(`Found ${response.data.total_count} total issues matching basic criteria`); - # + # # // Fetch full details for each issue to get labels, assignees, sub-issues, and linked PRs # // Track integrity-filtered issues to emit a diagnostic summary # const integrityFilteredIssues = []; @@ -250,7 +249,7 @@ on: # } # return null; # } - # + # # // Check if this issue has sub-issues and linked PRs using GraphQL # let subIssuesCount = 0; # let linkedPRs = []; @@ -288,9 +287,9 @@ on: # repo, # number: issue.number # }); - # + # # subIssuesCount = issueDetailsResult?.repository?.issue?.subIssues?.totalCount || 0; - # + # # // Extract linked PRs from timeline # const timelineItems = issueDetailsResult?.repository?.issue?.timelineItems?.nodes || []; # linkedPRs = timelineItems @@ -301,13 +300,13 @@ on: # isDraft: item.source.isDraft, # author: item.source.author?.login # })); - # + # # core.info(`Issue #${issue.number} has ${linkedPRs.length} linked PR(s)`); # } catch (error) { # // If GraphQL query fails, continue with defaults # core.warning(`Could not check details for #${issue.number}: ${error.message}`); # } - # + # # return { # ...fullIssue.data, # subIssuesCount, @@ -315,12 +314,12 @@ on: # }; # }) # )).filter(Boolean); // Remove null entries (integrity-filtered or otherwise skipped) - # + # # // Emit diagnostic summary for integrity-filtered issues # if (integrityFilteredIssues.length > 0) { # core.warning(`🛡️ Integrity filter diagnostic: ${integrityFilteredIssues.length} issue(s) were skipped due to integrity policy: #${integrityFilteredIssues.join(', #')}. These issues will be excluded from this run.`); # } - # + # # // Filter and score issues # const scoredIssues = issuesWithDetails # .filter(issue => { @@ -329,43 +328,43 @@ on: # core.info(`Skipping #${issue.number}: already has assignees`); # return false; # } - # + # # // Exclude issues with excluded labels (double check) # const issueLabels = issue.labels.map(l => l.name.toLowerCase()); # if (issueLabels.some(label => excludeLabels.map(l => l.toLowerCase()).includes(label))) { # core.info(`Skipping #${issue.number}: has excluded label`); # return false; # } - # + # # // Exclude issues that have sub-issues (parent/organizing issues) # if (issue.subIssuesCount > 0) { # core.info(`Skipping #${issue.number}: has ${issue.subIssuesCount} sub-issue(s) - parent issues are used for organizing, not tasks`); # return false; # } - # + # # // Exclude issues with closed PRs (treat as complete) # const closedPRs = issue.linkedPRs?.filter(pr => pr.state === 'CLOSED' || pr.state === 'MERGED') || []; # if (closedPRs.length > 0) { # core.info(`Skipping #${issue.number}: has ${closedPRs.length} closed/merged PR(s) - treating as complete`); # return false; # } - # + # # // Exclude issues with open PRs from Copilot coding agent - # const openCopilotPRs = issue.linkedPRs?.filter(pr => - # pr.state === 'OPEN' && + # const openCopilotPRs = issue.linkedPRs?.filter(pr => + # pr.state === 'OPEN' && # (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) # ) || []; # if (openCopilotPRs.length > 0) { # core.info(`Skipping #${issue.number}: has ${openCopilotPRs.length} open PR(s) from Copilot - already being worked on`); # return false; # } - # + # # return true; # }) # .map(issue => { # const issueLabels = issue.labels.map(l => l.name.toLowerCase()); # let score = 0; - # + # # // Score based on priority labels (higher score = higher priority) # if (issueLabels.includes('good first issue') || issueLabels.includes('good-first-issue')) { # score += 40; @@ -382,16 +381,16 @@ on: # if (issueLabels.includes('tech-debt') || issueLabels.includes('refactoring')) { # score += 45; # } - # + # # // Bonus for issues with clear labels (any priority label) # if (issueLabels.some(label => priorityLabels.map(l => l.toLowerCase()).includes(label))) { # score += 10; # } - # + # # // Age bonus: older issues get slight priority (days old / 10) # const ageInDays = Math.floor((Date.now() - new Date(issue.created_at)) / (1000 * 60 * 60 * 24)); # score += Math.min(ageInDays / 10, 20); // Cap age bonus at 20 points - # + # # return { # number: issue.number, # title: issue.title, @@ -414,7 +413,7 @@ on: # const labelStr = i.labels.length > 0 ? ` [${i.labels.join(', ')}]` : ''; # return `#${i.number}: ${i.title}${labelStr} (score: ${i.score.toFixed(1)}, base: ${i.base_branch})`; # }).join('\n'); - # + # # // Pre-fetch compact body context for top candidates so the agent can # // triage without extra reads in most runs. # const issueContext = branchFilteredIssues.slice(0, MAX_ISSUES_WITH_BODY_CONTEXT).map(i => { @@ -423,20 +422,20 @@ on: # const labelStr = i.labels.length > 0 ? i.labels.join(', ') : 'none'; # return `#${i.number} | score=${i.score.toFixed(1)} | base=${i.base_branch} | labels=${labelStr}\nTitle: ${i.title}\nBody: ${bodySnippet || '(no body)'}`; # }).join('\n\n---\n\n'); - # + # # const issueNumbers = branchFilteredIssues.map(i => i.number).join(','); - # + # # core.info(`Total candidate issues after branch filtering: ${branchFilteredIssues.length}`); # if (branchFilteredIssues.length > 0) { # core.info(`Top candidates:\n${issueList.split('\n').slice(0, 10).join('\n')}`); # } - # + # # core.setOutput('base_branch', selectedBaseBranch); # core.setOutput('issue_count', branchFilteredIssues.length); # core.setOutput('issue_numbers', issueNumbers); # core.setOutput('issue_list', issueList); # core.setOutput('issue_context', issueContext); - # + # # if (branchFilteredIssues.length === 0) { # core.info('🍽️ No suitable candidate issues - the plate is empty!'); # core.setOutput('has_issues', 'false'); @@ -693,7 +692,7 @@ jobs: - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - + GH_AW_PROMPT_9f2059a0f4cba3ed_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_with_safeoutputs_prompt.md" cat << 'GH_AW_PROMPT_9f2059a0f4cba3ed_EOF' @@ -744,9 +743,9 @@ jobs: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); - + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, @@ -802,6 +801,7 @@ jobs: needs: activation if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest + environment: issue-monster permissions: contents: read copilot-requests: write @@ -924,14 +924,15 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config env: + GH_AW_SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c5fc77ef820611b9_EOF' - {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_c5fc77ef820611b9_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_465f5279f1c0809c_EOF' + {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","github-token":"${GH_AW_SECRET_GH_AW_GITHUB_TOKEN}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_465f5279f1c0809c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1084,7 +1085,7 @@ jobs: run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="awmg-mcpg" @@ -1096,7 +1097,7 @@ jobs: mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" export DEBUG="*" - + export GH_AW_ENGINE="copilot" MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') @@ -1107,7 +1108,7 @@ jobs: esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.30' - + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) cat << GH_AW_MCP_CONFIG_4470e57135d8a598_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" @@ -1418,6 +1419,7 @@ jobs: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim + environment: issue-monster permissions: contents: read issues: write @@ -1665,6 +1667,7 @@ jobs: - agent if: always() && needs.agent.result != 'skipped' runs-on: ubuntu-latest + environment: issue-monster permissions: contents: read copilot-requests: write @@ -1909,6 +1912,7 @@ jobs: pre_activation: runs-on: ubuntu-slim + environment: issue-monster permissions: issues: read pull-requests: read @@ -1958,7 +1962,7 @@ jobs: env: GH_AW_SKIP_QUERY: "is:pr is:open is:draft author:app/copilot-swe-agent" GH_AW_WORKFLOW_NAME: "Issue Monster" - GH_AW_SKIP_MAX_MATCHES: "5" + GH_AW_SKIP_MAX_MATCHES: "1" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -2261,8 +2265,8 @@ jobs: } // Exclude issues with open PRs from Copilot coding agent - const openCopilotPRs = issue.linkedPRs?.filter(pr => - pr.state === 'OPEN' && + const openCopilotPRs = issue.linkedPRs?.filter(pr => + pr.state === 'OPEN' && (pr.author === 'copilot-swe-agent' || pr.author?.includes('copilot')) ) || []; if (openCopilotPRs.length > 0) { @@ -2370,6 +2374,7 @@ jobs: - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim + environment: issue-monster permissions: contents: read issues: write @@ -2452,8 +2457,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" - GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_AGENT_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 9341d81697df..b4f338c94f8c 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -418,6 +418,8 @@ sandbox: engine: id: copilot +environment: issue-monster + imports: - shared/github-guard-policy.md timeout-minutes: 30 @@ -448,6 +450,7 @@ safe-outputs: pull-request-repo: "${{ github.repository }}" allowed-pull-request-repos: ["${{ github.repository }}"] base-branch: "${{ needs.pre_activation.outputs.base_branch || 'main' }}" + github-token: "${{ secrets.GH_AW_GITHUB_TOKEN }}" allowed: [copilot] # Only allow copilot agent ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable add-comment: From 85cf469447d3dd154744f5798d8ad1a2236a5d93 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 14:29:23 -0700 Subject: [PATCH 09/10] Use assignment-only Issue Monster token --- .github/workflows/issue-monster.lock.yml | 21 ++++++++++++--------- .github/workflows/issue-monster.md | 3 ++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 21dbc8bffcb0..4e7c32334c43 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"de2f358ab7feacdfae3afc6ab6aae8e0535ecd78a82b429e7bed5cea9256190a","body_hash":"ba030040a3184a204a07c3bcde675f8dc04d1c2a69efee4eef0698ef7a5074bf","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} -# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0c40996701b8df6e57d2d7fabf8bd90aa021fe3eefcd090b2b98be97c7d711de","body_hash":"ba030040a3184a204a07c3bcde675f8dc04d1c2a69efee4eef0698ef7a5074bf","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","ISSUE_MONSTER_ASSIGNMENT_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -33,6 +33,7 @@ # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN +# - ISSUE_MONSTER_ASSIGNMENT_TOKEN # # Custom actions used: # - actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 @@ -924,15 +925,16 @@ jobs: run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11 ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config env: - GH_AW_SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_SECRET_ISSUE_MONSTER_ASSIGNMENT_TOKEN: ${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_465f5279f1c0809c_EOF' - {"add_comment":{"max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","github-token":"${GH_AW_SECRET_GH_AW_GITHUB_TOKEN}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_465f5279f1c0809c_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_83160e1bfc2e1298_EOF' + {"add_comment":{"github-token":"${GH_AW_SECRET_GITHUB_TOKEN}","max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","github-token":"${GH_AW_SECRET_ISSUE_MONSTER_ASSIGNMENT_TOKEN}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_83160e1bfc2e1298_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -1293,10 +1295,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN,ISSUE_MONSTER_ASSIGNMENT_TOKEN' SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SECRET_ISSUE_MONSTER_ASSIGNMENT_TOKEN: ${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }} - name: Append agent step summary if: always() run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" @@ -2457,8 +2460,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"github-token\":\"${{ secrets.GH_AW_GITHUB_TOKEN }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" - GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.GITHUB_TOKEN }}\",\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"github-token\":\"${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index b4f338c94f8c..0554947d0ce7 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -450,12 +450,13 @@ safe-outputs: pull-request-repo: "${{ github.repository }}" allowed-pull-request-repos: ["${{ github.repository }}"] base-branch: "${{ needs.pre_activation.outputs.base_branch || 'main' }}" - github-token: "${{ secrets.GH_AW_GITHUB_TOKEN }}" + github-token: "${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }}" allowed: [copilot] # Only allow copilot agent ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable add-comment: max: 3 target: "*" + github-token: "${{ secrets.GITHUB_TOKEN }}" noop: report-as-issue: false messages: From 4ac25b60856cef1618d4a284a3c128ead8f2ce98 Mon Sep 17 00:00:00 2001 From: Noah Gilson Date: Fri, 10 Jul 2026 14:50:11 -0700 Subject: [PATCH 10/10] fail if assignment does not work --- .github/workflows/issue-monster.lock.yml | 10 +++++----- .github/workflows/issue-monster.md | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/issue-monster.lock.yml b/.github/workflows/issue-monster.lock.yml index 4e7c32334c43..3e6f3202a30c 100644 --- a/.github/workflows/issue-monster.lock.yml +++ b/.github/workflows/issue-monster.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"0c40996701b8df6e57d2d7fabf8bd90aa021fe3eefcd090b2b98be97c7d711de","body_hash":"ba030040a3184a204a07c3bcde675f8dc04d1c2a69efee4eef0698ef7a5074bf","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b60148f739e05dbecf73b2fd2e9822975480ab2cc0d450c3fad3a5f08d5558e1","body_hash":"ba030040a3184a204a07c3bcde675f8dc04d1c2a69efee4eef0698ef7a5074bf","compiler_version":"v0.81.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.65"}} # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN","ISSUE_MONSTER_ASSIGNMENT_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba6380cc6e5be5d21677bebe04d52fb48e3abec7","version":"v0.81.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11","digest":"sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.11@sha256:979723c628182da7729333f2208bb249fd25ddee579645cf9a3892d681a929c7"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11","digest":"sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.11@sha256:807e4831999b44513b0a66e5859d478dc4da7ae74ab1918cec967d513f95bf9d"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.11"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11","digest":"sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.11@sha256:ff27ea0525ad953a6adee28a5fbe9d2e22be47dbec755c15767af4ea3f91df7d"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.30","digest":"sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.30@sha256:35625d1a2269b1238606078c879f59a91cffc4ac33eb54bf39c6418822c1a8be"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} # This file was automatically generated by gh-aw (v0.81.6). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -932,9 +932,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_83160e1bfc2e1298_EOF' - {"add_comment":{"github-token":"${GH_AW_SECRET_GITHUB_TOKEN}","max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","github-token":"${GH_AW_SECRET_ISSUE_MONSTER_ASSIGNMENT_TOKEN}","ignore-if-error":true,"max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_83160e1bfc2e1298_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_d3e7fbf5367030b3_EOF' + {"add_comment":{"github-token":"${GH_AW_SECRET_GITHUB_TOKEN}","max":3,"target":"*"},"assign_to_agent":{"allowed":["copilot"],"allowed-pull-request-repos":["${GITHUB_REPOSITORY}"],"base-branch":"${{ needs.pre_activation.outputs.base_branch || 'main' }}","github-token":"${GH_AW_SECRET_ISSUE_MONSTER_ASSIGNMENT_TOKEN}","max":3,"pull-request-repo":"${GITHUB_REPOSITORY}","target":"*","target-repo":"${GITHUB_REPOSITORY}"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_d3e7fbf5367030b3_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -2460,7 +2460,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,github.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.GITHUB_TOKEN }}\",\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"github-token\":\"${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }}\",\"ignore-if-error\":true,\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.GITHUB_TOKEN }}\",\"max\":3,\"target\":\"*\"},\"assign_to_agent\":{\"allowed\":[\"copilot\"],\"allowed-pull-request-repos\":[\"${{ github.repository }}\"],\"base-branch\":\"${{ needs.pre_activation.outputs.base_branch || 'main' }}\",\"github-token\":\"${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }}\",\"max\":3,\"pull-request-repo\":\"${{ github.repository }}\",\"target\":\"*\",\"target-repo\":\"${{ github.repository }}\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}" GH_AW_ASSIGN_TO_AGENT_TOKEN: ${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-monster.md b/.github/workflows/issue-monster.md index 0554947d0ce7..f29a84a9b578 100644 --- a/.github/workflows/issue-monster.md +++ b/.github/workflows/issue-monster.md @@ -452,7 +452,6 @@ safe-outputs: base-branch: "${{ needs.pre_activation.outputs.base_branch || 'main' }}" github-token: "${{ secrets.ISSUE_MONSTER_ASSIGNMENT_TOKEN }}" allowed: [copilot] # Only allow copilot agent - ignore-if-error: true # Don't fail the workflow if copilot is temporarily unavailable add-comment: max: 3 target: "*"