From 4375e744c9300641396811de0f06c194141fadac Mon Sep 17 00:00:00 2001 From: mulder Date: Wed, 25 Feb 2026 19:16:40 -0500 Subject: [PATCH 1/7] Add Copilot auto-issues workflow for automated code analysis - Add GitHub Actions workflow (copilot-auto-issues) triggered on push to master and manual dispatch - Workflow analyzes commits for bugs, security issues, code smells, missing tests, docs gaps, and performance problems - Automatically creates labeled GitHub issues with detailed context, reproduction steps, and suggested fixes - Includes compiled lock.yml workflow definition generated by gh-aw - Add .gitattributes for linguist-generated marking of lock files - Add actions-lock.json for pinned action versions --- .gitattributes | 1 + .github/aw/actions-lock.json | 14 + .../workflows/copilot-auto-issues.lock.yml | 1105 +++++++++++++++++ .github/workflows/copilot-auto-issues.md | 61 + 4 files changed, 1181 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/aw/actions-lock.json create mode 100644 .github/workflows/copilot-auto-issues.lock.yml create mode 100644 .github/workflows/copilot-auto-issues.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..c1965c21 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 00000000..83a65c52 --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,14 @@ +{ + "entries": { + "actions/github-script@v8": { + "repo": "actions/github-script", + "version": "v8", + "sha": "ed597411d8f924073f98dfc5c65a23a2325f34cd" + }, + "github/gh-aw/actions/setup@v0.50.4": { + "repo": "github/gh-aw/actions/setup", + "version": "v0.50.4", + "sha": "90ebf8057e8e005103b8d123732d2c64c30e9b27" + } + } +} diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-auto-issues.lock.yml new file mode 100644 index 00000000..afb8d4db --- /dev/null +++ b/.github/workflows/copilot-auto-issues.lock.yml @@ -0,0 +1,1105 @@ +# +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.50.4). DO NOT EDIT. +# +# 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/ +# +# +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"1170d5c0c20eaa2ae48ad093e1e811e341cab45bbce31a4e085515fa4a34b0ea","compiler_version":"v0.50.4"} + +name: "Copilot Auto Issues" +"on": + push: + branches: + - master + workflow_dispatch: + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.ref }}" + +run-name: "Copilot Auto Issues" + +jobs: + activation: + needs: pre_activation + if: needs.pre_activation.outputs.activated == 'true' + runs-on: self-hosted + permissions: + contents: read + outputs: + comment_id: "" + comment_repo: "" + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 + with: + destination: /opt/gh-aw/actions + - name: Validate context variables + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs'); + await main(); + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + sparse-checkout: | + .github + .agents + fetch-depth: 1 + persist-credentials: false + - name: Check workflow file timestamps + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_WORKFLOW_FILE: "copilot-auto-issues.lock.yml" + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + run: | + bash /opt/gh-aw/actions/create_prompt_first.sh + { + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat "/opt/gh-aw/prompts/xpia.md" + cat "/opt/gh-aw/prompts/temp_folder_prompt.md" + cat "/opt/gh-aw/prompts/markdown.md" + cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_EOF' + + Tools: create_issue, missing_tool, missing_data + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + + GH_AW_PROMPT_EOF + cat << 'GH_AW_PROMPT_EOF' + {{#runtime-import .github/workflows/copilot-auto-issues.md}} + GH_AW_PROMPT_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + 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_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + run: bash /opt/gh-aw/actions/print_prompt_summary.sh + - name: Upload prompt artifact + if: success() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts/prompt.txt + retention-days: 1 + + agent: + needs: activation + runs-on: + - self-hosted + permissions: + contents: read + 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_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_WORKFLOW_ID_SANITIZED: copilotautoissues + outputs: + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + model: ${{ steps.generate_aw_info.outputs.model }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 + with: + destination: /opt/gh-aw/actions + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + 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('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Generate agentic run info + id: generate_aw_info + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const fs = require('fs'); + + const awInfo = { + engine_id: "copilot", + engine_name: "GitHub Copilot CLI", + model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", + version: "", + agent_version: "0.0.417", + cli_version: "v0.50.4", + workflow_name: "Copilot Auto Issues", + experimental: false, + supports_tools_allowlist: true, + run_id: context.runId, + run_number: context.runNumber, + run_attempt: process.env.GITHUB_RUN_ATTEMPT, + repository: context.repo.owner + '/' + context.repo.repo, + ref: context.ref, + sha: context.sha, + actor: context.actor, + event_name: context.eventName, + staged: false, + allowed_domains: ["defaults"], + firewall_enabled: true, + awf_version: "v0.23.0", + awmg_version: "v0.1.5", + steps: { + firewall: "squid" + }, + created_at: new Date().toISOString() + }; + + // Write to /tmp/gh-aw directory to avoid inclusion in PR + const tmpPath = '/tmp/gh-aw/aw_info.json'; + fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); + console.log('Generated aw_info.json at:', tmpPath); + console.log(JSON.stringify(awInfo, null, 2)); + + // Set model as output for reuse in other steps/jobs + core.setOutput('model', awInfo.model); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Install GitHub Copilot CLI + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.417 + - name: Install awf binary + run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.23.0 + - name: Determine automatic lockdown mode for GitHub MCP Server + id: determine-automatic-lockdown + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + with: + script: | + const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); + await determineAutomaticLockdown(github, context, core); + - name: Download container images + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.23.0 ghcr.io/github/gh-aw-firewall/api-proxy:0.23.0 ghcr.io/github/gh-aw-firewall/squid:0.23.0 ghcr.io/github/gh-aw-mcpg:v0.1.5 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p /opt/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' + {"create_issue":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_EOF + cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' + [ + { + "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[copilot-auto] \". Labels [copilot auto-generated bug enhancement tech-debt] will be automatically added.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", + "type": "string" + }, + "labels": { + "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", + "items": { + "type": "string" + }, + "type": "array" + }, + "parent": { + "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.", + "type": [ + "number", + "string" + ] + }, + "temporary_id": { + "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 8 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.", + "pattern": "^aw_[A-Za-z0-9]{3,8}$", + "type": "string" + }, + "title": { + "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_issue" + }, + { + "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "reason": { + "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).", + "type": "string" + }, + "tool": { + "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.", + "type": "string" + } + }, + "required": [ + "reason" + ], + "type": "object" + }, + "name": "missing_tool" + }, + { + "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "message": { + "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').", + "type": "string" + } + }, + "required": [ + "message" + ], + "type": "object" + }, + "name": "noop" + }, + { + "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "alternatives": { + "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).", + "type": "string" + }, + "context": { + "description": "Additional context about the missing data or where it should come from (max 256 characters).", + "type": "string" + }, + "data_type": { + "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.", + "type": "string" + }, + "reason": { + "description": "Explanation of why this data is needed to complete the task (max 256 characters).", + "type": "string" + } + }, + "required": [], + "type": "object" + }, + "name": "missing_data" + } + ] + GH_AW_SAFE_OUTPUTS_TOOLS_EOF + cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "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 + } + } + } + } + GH_AW_SAFE_OUTPUTS_VALIDATION_EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + 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 DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -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_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_LOCKDOWN -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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.1.5' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.31.0", + "env": { + "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_EOF + - name: Generate workflow overview + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs'); + await generateWorkflowOverview(core); + - name: Download prompt artifact + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: prompt + path: /tmp/gh-aw/aw-prompts + - name: Clean git credentials + run: bash /opt/gh-aw/actions/clean_git_credentials.sh + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-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" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-all-tools --allow-all-paths --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_AGENT_COPILOT:+ --model "$GH_AW_MODEL_AGENT_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MODEL_AGENT_COPILOT: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + 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_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: | + # Copy Copilot session state files to logs folder for artifact collection + # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them + SESSION_STATE_DIR="$HOME/.copilot/session-state" + LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" + + if [ -d "$SESSION_STATE_DIR" ]; then + echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" + mkdir -p "$LOGS_DIR" + cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true + echo "Session state files copied successfully" + else + echo "No session-state directory found at $SESSION_STATE_DIR" + fi + - 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 /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_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: Upload Safe Outputs + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output + path: ${{ env.GH_AW_SAFE_OUTPUTS }} + if-no-files-found: warn + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_SAFE_OUTPUTS: ${{ env.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" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Upload sanitized agent output + if: always() && env.GH_AW_AGENT_OUTPUT + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-output + path: ${{ env.GH_AW_AGENT_OUTPUT }} + if-no-files-found: warn + - name: Upload engine output files + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent_outputs + path: | + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + if-no-files-found: ignore + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/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: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # 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: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: agent-artifacts + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + if-no-files-found: ignore + # --- Threat Detection (inline) --- + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ steps.collect_output.outputs.output_types }} + HAS_PATCH: ${{ steps.collect_output.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 configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.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 + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + 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 + 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + WORKFLOW_NAME: "Copilot Auto Issues" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/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: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + # --allow-tool shell(cat) + # --allow-tool shell(grep) + # --allow-tool shell(head) + # --allow-tool shell(jq) + # --allow-tool shell(ls) + # --allow-tool shell(tail) + # --allow-tool shell(wc) + timeout-minutes: 20 + run: | + set -o pipefail + # shellcheck disable=SC1003 + sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,raw.githubusercontent.com,registry.npmjs.org,telemetry.enterprise.githubcopilot.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \ + -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(wc)'\'' --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"${GH_AW_MODEL_DETECTION_COPILOT:+ --model "$GH_AW_MODEL_DETECTION_COPILOT"}' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_MODEL_DETECTION_COPILOT: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }} + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }} + GITHUB_WORKSPACE: ${{ github.workspace }} + XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection results + id: parse_detection_results + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: threat-detection.log + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Set detection conclusion + id: detection_conclusion + if: always() + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_SUCCESS: ${{ steps.parse_detection_results.outputs.success }} + run: | + if [[ "$RUN_DETECTION" != "true" ]]; then + echo "conclusion=skipped" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection was not needed, marking as skipped" + elif [[ "$DETECTION_SUCCESS" == "true" ]]; then + echo "conclusion=success" >> "$GITHUB_OUTPUT" + echo "success=true" >> "$GITHUB_OUTPUT" + echo "Detection passed successfully" + else + echo "conclusion=failure" >> "$GITHUB_OUTPUT" + echo "success=false" >> "$GITHUB_OUTPUT" + echo "Detection found issues" + fi + + conclusion: + needs: + - activation + - agent + - safe_outputs + if: (always()) && (needs.agent.result != 'skipped') + runs-on: self-hosted + permissions: + contents: read + issues: write + outputs: + 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 + uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process No-Op Messages + id: noop + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/noop.cjs'); + await main(); + - name: Record Missing Tool + id: missing_tool + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Handle Agent Failure + id: handle_agent_failure + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + 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: "copilot-auto-issues" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_GROUP_REPORTS: "false" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Handle No-Op Message + id: handle_noop_message + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs'); + await main(); + + pre_activation: + runs-on: self-hosted + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} + matched_command: '' + steps: + - name: Setup Scripts + uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 + with: + destination: /opt/gh-aw/actions + - name: Check team membership for workflow + id: check_membership + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_REQUIRED_ROLES: admin,maintainer,write + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/check_membership.cjs'); + await main(); + + safe_outputs: + needs: agent + if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.agent.outputs.detection_success == 'true') + runs-on: self-hosted + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_ENGINE_ID: "copilot" + GH_AW_WORKFLOW_ID: "copilot-auto-issues" + GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + outputs: + 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 }} + 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 + uses: github/gh-aw/actions/setup@90ebf8057e8e005103b8d123732d2c64c30e9b27 # v0.50.4 + with: + destination: /opt/gh-aw/actions + - name: Download agent output artifact + continue-on-error: true + uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6 + with: + name: agent-output + path: /tmp/gh-aw/safeoutputs/ + - name: Setup agent output environment variable + run: | + mkdir -p /tmp/gh-aw/safeoutputs/ + find "/tmp/gh-aw/safeoutputs/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"labels\":[\"copilot\",\"auto-generated\",\"bug\",\"enhancement\",\"tech-debt\"],\"max\":10,\"title_prefix\":\"[copilot-auto] \"},\"missing_data\":{},\"missing_tool\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload safe output items manifest + if: always() + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 + with: + name: safe-output-items + path: /tmp/safe-output-items.jsonl + if-no-files-found: warn + diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md new file mode 100644 index 00000000..35088a56 --- /dev/null +++ b/.github/workflows/copilot-auto-issues.md @@ -0,0 +1,61 @@ +--- +on: + push: + branches: [ "master" ] + workflow_dispatch: + +runs-on: [self-hosted] + +permissions: + contents: read + issues: read + pull-requests: read + +safe-outputs: + create-issue: + title-prefix: "[copilot-auto] " + labels: [copilot, auto-generated, bug, enhancement, tech-debt] + close-older-issues: false +--- +## Automatic Copilot Issue Evaluation on Push to master (or Manual Trigger) + +**Triggers:** +- Every push to `master` +- **Manually** — Actions tab → select this workflow → "Run workflow" + (or run `gh aw run copilot-auto-issues` from terminal) + +On every trigger, Copilot will automatically: +- Analyze the latest commit(s) on `master` +- Detect bugs, security issues, code smells, missing tests, docs gaps, performance problems, formatting issues, etc. +- Create one or more actionable GitHub issues +- Assign them to the person who pushed/triggered it (`github.actor`) +- Tell Copilot to start working on the fix immediately + +### What Copilot must do + +1. **Determine scope** + - On push → use the push event commits + - On manual trigger → analyze current `HEAD` of `master` + +2. **Full analysis** + - Commit messages, changed files, full diff + - Context from repository (CODEOWNERS, tests, docs, etc.) + +3. **Create issues for every problem found** + - Title starts with `[copilot-auto] ` + - Detailed body with: + - Exact commit SHA + files + - Why it's an issue + - Reproduction steps (if applicable) + - Suggested fix + code snippet + - Labels: `copilot`, `auto-generated`, plus `bug` / `security` / `tech-debt` / `good-first-issue` etc. + - **Assign to** the pusher/triggerer (`${{ github.actor }}`) + - Add this pinned comment at the top of the issue: @copilot Please start working on this immediately. Open a draft PR with the fix when ready. + +4. **If the fix is straightforward** +- Instruct Copilot to implement it and open a linked **draft PR** to the issue. + +5. **Summary** +- If nothing found → create a short "✅ All clear on master" issue (or just log it) +- Always link back to the triggering commit / workflow run + From 5cc48b1ad9f005d7db4479448052fa37e7f200fd Mon Sep 17 00:00:00 2001 From: mulder Date: Wed, 25 Feb 2026 19:20:27 -0500 Subject: [PATCH 2/7] Remove workflow_dispatch trigger from copilot-auto-issues workflow --- .github/workflows/copilot-auto-issues.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md index 35088a56..64b487e0 100644 --- a/.github/workflows/copilot-auto-issues.md +++ b/.github/workflows/copilot-auto-issues.md @@ -1,8 +1,7 @@ --- on: push: - branches: [ "master" ] - workflow_dispatch: + branches: [ "master" ] runs-on: [self-hosted] From cec5ad9629c02947212e12f9a12f374fcffe7507 Mon Sep 17 00:00:00 2001 From: mulder Date: Wed, 25 Feb 2026 19:23:13 -0500 Subject: [PATCH 3/7] Fix formatting: remove trailing blank lines and whitespace --- .github/workflows/copilot-auto-issues.lock.yml | 4 +--- .github/workflows/copilot-auto-issues.md | 5 ++--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-auto-issues.lock.yml index afb8d4db..53e8f583 100644 --- a/.github/workflows/copilot-auto-issues.lock.yml +++ b/.github/workflows/copilot-auto-issues.lock.yml @@ -30,7 +30,6 @@ name: "Copilot Auto Issues" branches: - master workflow_dispatch: - permissions: {} concurrency: @@ -1101,5 +1100,4 @@ jobs: with: name: safe-output-items path: /tmp/safe-output-items.jsonl - if-no-files-found: warn - + if-no-files-found: warn \ No newline at end of file diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md index 64b487e0..ccac847f 100644 --- a/.github/workflows/copilot-auto-issues.md +++ b/.github/workflows/copilot-auto-issues.md @@ -20,7 +20,7 @@ safe-outputs: **Triggers:** - Every push to `master` -- **Manually** — Actions tab → select this workflow → "Run workflow" +- **Manually** — Actions tab → select this workflow → "Run workflow" (or run `gh aw run copilot-auto-issues` from terminal) On every trigger, Copilot will automatically: @@ -56,5 +56,4 @@ On every trigger, Copilot will automatically: 5. **Summary** - If nothing found → create a short "✅ All clear on master" issue (or just log it) -- Always link back to the triggering commit / workflow run - +- Always link back to the triggering commit / workflow run \ No newline at end of file From a297b42bf0f53a1ead1241134f4f77c0f6286cfd Mon Sep 17 00:00:00 2001 From: mulder Date: Wed, 25 Feb 2026 19:25:05 -0500 Subject: [PATCH 4/7] Remove workflow_dispatch from lock.yml and fix trailing whitespace --- .github/workflows/copilot-auto-issues.lock.yml | 1 - .github/workflows/copilot-auto-issues.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-auto-issues.lock.yml index 53e8f583..1eb07d36 100644 --- a/.github/workflows/copilot-auto-issues.lock.yml +++ b/.github/workflows/copilot-auto-issues.lock.yml @@ -29,7 +29,6 @@ name: "Copilot Auto Issues" push: branches: - master - workflow_dispatch: permissions: {} concurrency: diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md index ccac847f..33819bd6 100644 --- a/.github/workflows/copilot-auto-issues.md +++ b/.github/workflows/copilot-auto-issues.md @@ -1,7 +1,7 @@ --- on: push: - branches: [ "master" ] + branches: [ "master" ] runs-on: [self-hosted] From e8a417573bd7c8b2ab2ad57a8349345b368c93db Mon Sep 17 00:00:00 2001 From: mulder Date: Wed, 25 Feb 2026 19:25:57 -0500 Subject: [PATCH 5/7] Fix pre-commit: add final newlines and remove trailing whitespace --- .gitattributes | 2 +- .../workflows/copilot-auto-issues.lock.yml | 44 +++++++++---------- .github/workflows/copilot-auto-issues.md | 2 +- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.gitattributes b/.gitattributes index c1965c21..1b06f3eb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1 @@ -.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file +.github/workflows/*.lock.yml linguist-generated=true merge=ours diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-auto-issues.lock.yml index 1eb07d36..a80c68f2 100644 --- a/.github/workflows/copilot-auto-issues.lock.yml +++ b/.github/workflows/copilot-auto-issues.lock.yml @@ -1,12 +1,12 @@ # -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| @@ -130,7 +130,7 @@ jobs: - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - + GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' @@ -167,9 +167,9 @@ jobs: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); - + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, @@ -271,7 +271,7 @@ jobs: with: script: | const fs = require('fs'); - + const awInfo = { engine_id: "copilot", engine_name: "GitHub Copilot CLI", @@ -300,13 +300,13 @@ jobs: }, created_at: new Date().toISOString() }; - + // Write to /tmp/gh-aw directory to avoid inclusion in PR const tmpPath = '/tmp/gh-aw/aw_info.json'; fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); console.log('Generated aw_info.json at:', tmpPath); console.log(JSON.stringify(awInfo, null, 2)); - + // Set model as output for reuse in other steps/jobs core.setOutput('model', awInfo.model); - name: Validate COPILOT_GITHUB_TOKEN secret @@ -553,17 +553,17 @@ jobs: # Mask immediately to prevent timing vulnerabilities API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${API_KEY}" - + PORT=3001 - + # Set outputs for next steps { echo "safe_outputs_api_key=${API_KEY}" echo "safe_outputs_port=${PORT}" } >> "$GITHUB_OUTPUT" - + echo "Safe Outputs MCP server will run on port ${PORT}" - + - name: Start Safe Outputs MCP HTTP Server id: safe-outputs-start env: @@ -581,9 +581,9 @@ jobs: export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - + bash /opt/gh-aw/actions/start_safe_outputs_server.sh - + - name: Start MCP Gateway id: start-mcp-gateway env: @@ -595,7 +595,7 @@ jobs: run: | set -eo pipefail mkdir -p /tmp/gh-aw/mcp-config - + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="80" export MCP_GATEWAY_DOMAIN="host.docker.internal" @@ -605,10 +605,10 @@ jobs: export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export DEBUG="*" - + export GH_AW_ENGINE="copilot" export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -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_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_LOCKDOWN -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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.1.5' - + mkdir -p /home/runner/.copilot cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -694,7 +694,7 @@ jobs: # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them SESSION_STATE_DIR="$HOME/.copilot/session-state" LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - + if [ -d "$SESSION_STATE_DIR" ]; then echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" mkdir -p "$LOGS_DIR" @@ -1099,4 +1099,4 @@ jobs: with: name: safe-output-items path: /tmp/safe-output-items.jsonl - if-no-files-found: warn \ No newline at end of file + if-no-files-found: warn diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md index 33819bd6..8cc03e70 100644 --- a/.github/workflows/copilot-auto-issues.md +++ b/.github/workflows/copilot-auto-issues.md @@ -56,4 +56,4 @@ On every trigger, Copilot will automatically: 5. **Summary** - If nothing found → create a short "✅ All clear on master" issue (or just log it) -- Always link back to the triggering commit / workflow run \ No newline at end of file +- Always link back to the triggering commit / workflow run From e0924deaaf16ad60a5bf47b55349ffc7c74f7177 Mon Sep 17 00:00:00 2001 From: mulder Date: Thu, 26 Feb 2026 11:08:34 -0500 Subject: [PATCH 6/7] Fix: trigger on test/CI failures instead of push to master - Change trigger from push-to-master to workflow_run (Run tests, CodeQL, dynamic) - Add fork validation and dangerous-triggers security annotations - Add workflow_run HTML URL to environment for issue context - Simplify concurrency group to workflow-level --- .../workflows/copilot-auto-issues.lock.yml | 79 ++++++++----- .github/workflows/copilot-auto-issues.md | 107 +++++++++++++----- 2 files changed, 125 insertions(+), 61 deletions(-) diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-auto-issues.lock.yml index a80c68f2..f6fb9c4e 100644 --- a/.github/workflows/copilot-auto-issues.lock.yml +++ b/.github/workflows/copilot-auto-issues.lock.yml @@ -1,12 +1,12 @@ # -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ # | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ +# | | | | (_| | __/ | | | |_| | (__ # \_| |_/\__, |\___|_| |_|\__|_|\___| # __/ | -# _ _ |___/ +# _ _ |___/ # | | | | / _| | # | | | | ___ _ __ _ __| |_| | _____ ____ # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| @@ -22,24 +22,36 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"1170d5c0c20eaa2ae48ad093e1e811e341cab45bbce31a4e085515fa4a34b0ea","compiler_version":"v0.50.4"} +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"39d497736405f21b5e96b401a9b58b51753ff07080b1b385664a2f336261a05c","compiler_version":"v0.50.4"} name: "Copilot Auto Issues" "on": - push: + workflow_dispatch: + workflow_run: + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation branches: - - master + - main + types: + - completed + workflows: + - Run tests + - CodeQL + - dynamic + permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.ref }}" + group: "gh-aw-${{ github.workflow }}" run-name: "Copilot Auto Issues" jobs: activation: needs: pre_activation - if: needs.pre_activation.outputs.activated == 'true' + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation + if: > + (needs.pre_activation.outputs.activated == 'true') && ((github.event_name != 'workflow_run') || ((github.event.workflow_run.repository.id == github.repository_id) && + (!(github.event.workflow_run.repository.fork)))) runs-on: self-hosted permissions: contents: read @@ -86,6 +98,7 @@ jobs: GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -130,7 +143,7 @@ jobs: - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - + GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' @@ -144,6 +157,7 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -159,6 +173,7 @@ jobs: GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -167,9 +182,9 @@ jobs: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io); - + const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs'); - + // Call the substitution function return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, @@ -179,6 +194,7 @@ jobs: GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL: process.env.GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL, 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, @@ -203,12 +219,14 @@ jobs: agent: needs: activation - runs-on: - - self-hosted + runs-on: self-hosted permissions: + actions: read contents: read issues: read pull-requests: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" @@ -271,7 +289,7 @@ jobs: with: script: | const fs = require('fs'); - + const awInfo = { engine_id: "copilot", engine_name: "GitHub Copilot CLI", @@ -300,13 +318,13 @@ jobs: }, created_at: new Date().toISOString() }; - + // Write to /tmp/gh-aw directory to avoid inclusion in PR const tmpPath = '/tmp/gh-aw/aw_info.json'; fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2)); console.log('Generated aw_info.json at:', tmpPath); console.log(JSON.stringify(awInfo, null, 2)); - + // Set model as output for reuse in other steps/jobs core.setOutput('model', awInfo.model); - name: Validate COPILOT_GITHUB_TOKEN secret @@ -341,7 +359,7 @@ jobs: cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ { - "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[copilot-auto] \". Labels [copilot auto-generated bug enhancement tech-debt] will be automatically added.", + "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[copilot-auto] \". Labels [copilot auto-generated bug] will be automatically added.", "inputSchema": { "additionalProperties": false, "properties": { @@ -553,17 +571,17 @@ jobs: # Mask immediately to prevent timing vulnerabilities API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${API_KEY}" - + PORT=3001 - + # Set outputs for next steps { echo "safe_outputs_api_key=${API_KEY}" echo "safe_outputs_port=${PORT}" } >> "$GITHUB_OUTPUT" - + echo "Safe Outputs MCP server will run on port ${PORT}" - + - name: Start Safe Outputs MCP HTTP Server id: safe-outputs-start env: @@ -581,9 +599,9 @@ jobs: export GH_AW_SAFE_OUTPUTS_TOOLS_PATH export GH_AW_SAFE_OUTPUTS_CONFIG_PATH export GH_AW_MCP_LOG_DIR - + bash /opt/gh-aw/actions/start_safe_outputs_server.sh - + - name: Start MCP Gateway id: start-mcp-gateway env: @@ -595,7 +613,7 @@ jobs: run: | set -eo pipefail mkdir -p /tmp/gh-aw/mcp-config - + # Export gateway environment variables for MCP config and gateway script export MCP_GATEWAY_PORT="80" export MCP_GATEWAY_DOMAIN="host.docker.internal" @@ -605,10 +623,10 @@ jobs: export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" export DEBUG="*" - + export GH_AW_ENGINE="copilot" export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -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_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_LOCKDOWN -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 GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -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 ghcr.io/github/gh-aw-mcpg:v0.1.5' - + mkdir -p /home/runner/.copilot cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -694,7 +712,7 @@ jobs: # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them SESSION_STATE_DIR="$HOME/.copilot/session-state" LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs" - + if [ -d "$SESSION_STATE_DIR" ]; then echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR" mkdir -p "$LOGS_DIR" @@ -1085,7 +1103,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"labels\":[\"copilot\",\"auto-generated\",\"bug\",\"enhancement\",\"tech-debt\"],\"max\":10,\"title_prefix\":\"[copilot-auto] \"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"labels\":[\"copilot\",\"auto-generated\",\"bug\"],\"max\":10,\"title_prefix\":\"[copilot-auto] \"},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1100,3 +1118,4 @@ jobs: name: safe-output-items path: /tmp/safe-output-items.jsonl if-no-files-found: warn + diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md index 8cc03e70..c512dd1b 100644 --- a/.github/workflows/copilot-auto-issues.md +++ b/.github/workflows/copilot-auto-issues.md @@ -1,59 +1,104 @@ --- on: - push: - branches: [ "master" ] - -runs-on: [self-hosted] + workflow_run: + workflows: ["Run tests", "CodeQL", "dynamic"] + types: [completed] + branches: [main] + workflow_dispatch: permissions: contents: read issues: read pull-requests: read + actions: read safe-outputs: create-issue: title-prefix: "[copilot-auto] " - labels: [copilot, auto-generated, bug, enhancement, tech-debt] + labels: [copilot, auto-generated, bug] close-older-issues: false + max: 10 --- -## Automatic Copilot Issue Evaluation on Push to master (or Manual Trigger) +## Automatic Copilot Issue Creation on CI Failures **Triggers:** -- Every push to `master` +- **Automatically** when any of these workflows **completes**: + - **Run tests** — pytest matrix across 3 OSes × 2 Python versions + - **CodeQL** — security / code scanning analysis + - **dynamic** — PyPI package publish - **Manually** — Actions tab → select this workflow → "Run workflow" (or run `gh aw run copilot-auto-issues` from terminal) -On every trigger, Copilot will automatically: -- Analyze the latest commit(s) on `master` -- Detect bugs, security issues, code smells, missing tests, docs gaps, performance problems, formatting issues, etc. -- Create one or more actionable GitHub issues -- Assign them to the person who pushed/triggered it (`github.actor`) +**Important:** This workflow fires whenever one of the watched workflows finishes. +It checks `github.event.workflow_run.conclusion` and only creates issues when +the conclusion is `failure`. Use `github.event.workflow_run.name` to determine +which workflow failed and tailor the response accordingly. + +On trigger, Copilot will automatically: +- Determine which workflow failed (tests, CodeQL, or PyPI publish) +- Gather failure details from artifacts or workflow logs +- Create actionable GitHub issues for each distinct failure +- Assign them to the person whose push caused the failure (`github.actor`) - Tell Copilot to start working on the fix immediately ### What Copilot must do -1. **Determine scope** - - On push → use the push event commits - - On manual trigger → analyze current `HEAD` of `master` +1. **Gate on failure** (skip if CI passed) + - Check `github.event.workflow_run.conclusion` + - If conclusion is NOT `failure`, skip all steps and log "CI passed — nothing to do" + - On manual trigger → fetch the latest runs for all three watched workflows via the API + +2. **Identify which workflow failed** + - Read `github.event.workflow_run.name` to determine the source: + - `"Run tests"` → test failure path (step 3a) + - `"CodeQL"` → security / code scanning failure path (step 3b) + - `"dynamic"` → PyPI publish failure path (step 3c) + - Identify the commit SHA and branch from `github.event.workflow_run.head_sha` -2. **Full analysis** - - Commit messages, changed files, full diff - - Context from repository (CODEOWNERS, tests, docs, etc.) +3a. **Test failures ("Run tests")** + - Download ALL `test-results-*` artifacts from the triggering workflow run + (one per matrix combination, e.g. `test-results-ubuntu-latest-py3.11`) + - Parse each `test-results.xml` (JUnit XML) to extract: + - Failed test names and their module/file paths + - Error messages and stack traces + - Which OS + Python version the failure occurred on + - Also read each `test-output.txt` for full pytest verbose output + - Note which platforms passed vs failed — a test that only fails on Windows + or macOS is a platform-specific bug and should be labeled accordingly + - Create one issue per distinct failure (or group related failures): + - Labels: `test-failure`, plus `platform-specific` if OS-specific + - Body includes: exact test name(s), error + stack trace, OS/Python matrix, root cause, fix snippet -3. **Create issues for every problem found** +3b. **CodeQL / code scanning failures ("CodeQL")** + - Retrieve the SARIF results or workflow logs from the failed run + - Identify which CodeQL query/rule triggered the failure + - Create one issue per distinct security finding: + - Labels: `security` + - Body includes: rule ID, severity, affected file(s) + line(s), explanation, suggested remediation + +3c. **PyPI publish failures ("dynamic")** + - Retrieve the workflow logs to identify the publish error + - Common causes: version conflict, auth failure, build error, missing metadata + - Create one issue: + - Labels: `publish`, `packaging` + - Body includes: full error from the publish step, likely cause, fix steps + +4. **All issue types — common fields** - Title starts with `[copilot-auto] ` - - Detailed body with: - - Exact commit SHA + files - - Why it's an issue - - Reproduction steps (if applicable) - - Suggested fix + code snippet - - Labels: `copilot`, `auto-generated`, plus `bug` / `security` / `tech-debt` / `good-first-issue` etc. + - Body always includes: + - Triggering commit SHA + link to the failed workflow run + - The source workflow that failed + - The source file(s) likely causing the failure + - Suggested fix with code snippet + - Base labels: `copilot`, `auto-generated`, `bug` - **Assign to** the pusher/triggerer (`${{ github.actor }}`) - - Add this pinned comment at the top of the issue: @copilot Please start working on this immediately. Open a draft PR with the fix when ready. + - Add this pinned comment at the top of the issue: + > @copilot Please start working on this immediately. Open a draft PR with the fix when ready. + +5. **If the fix is straightforward** + - Instruct Copilot to implement it and open a linked **draft PR** to the issue. -4. **If the fix is straightforward** -- Instruct Copilot to implement it and open a linked **draft PR** to the issue. +6. **Summary** + - If the workflow actually passed (guard) → log "All clear" and exit + - Always link back to the triggering workflow run: `${{ github.event.workflow_run.html_url }}` -5. **Summary** -- If nothing found → create a short "✅ All clear on master" issue (or just log it) -- Always link back to the triggering commit / workflow run From 84a3d77fd474276b722ea8be5645e7d97000cfaa Mon Sep 17 00:00:00 2001 From: mulder Date: Thu, 26 Feb 2026 11:36:58 -0500 Subject: [PATCH 7/7] Replace copilot-auto-issues with copilot-autofix workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaced the generic code-analysis-and-issue-creation workflow with a targeted autofix workflow that responds to test failures on PRs: - Renamed workflow from copilot-auto-issues to copilot-autofix - Triggers on 'Run tests' workflow_run completion (not push) - Gates on failure conclusion — does nothing when tests pass - Posts fix suggestions directly on the failing PR (not new issues) - Uses GitHub suggestion blocks for one-click acceptance - Analyzes JUnit XML test artifacts to identify root causes - Scoped to only fix what's needed to make failing tests pass --- .github/workflows/copilot-auto-issues.md | 104 ------------------ ...sues.lock.yml => copilot-autofix.lock.yml} | 97 ++++++---------- .github/workflows/copilot-autofix.md | 81 ++++++++++++++ 3 files changed, 112 insertions(+), 170 deletions(-) delete mode 100644 .github/workflows/copilot-auto-issues.md rename .github/workflows/{copilot-auto-issues.lock.yml => copilot-autofix.lock.yml} (92%) create mode 100644 .github/workflows/copilot-autofix.md diff --git a/.github/workflows/copilot-auto-issues.md b/.github/workflows/copilot-auto-issues.md deleted file mode 100644 index c512dd1b..00000000 --- a/.github/workflows/copilot-auto-issues.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -on: - workflow_run: - workflows: ["Run tests", "CodeQL", "dynamic"] - types: [completed] - branches: [main] - workflow_dispatch: - -permissions: - contents: read - issues: read - pull-requests: read - actions: read - -safe-outputs: - create-issue: - title-prefix: "[copilot-auto] " - labels: [copilot, auto-generated, bug] - close-older-issues: false - max: 10 ---- -## Automatic Copilot Issue Creation on CI Failures - -**Triggers:** -- **Automatically** when any of these workflows **completes**: - - **Run tests** — pytest matrix across 3 OSes × 2 Python versions - - **CodeQL** — security / code scanning analysis - - **dynamic** — PyPI package publish -- **Manually** — Actions tab → select this workflow → "Run workflow" - (or run `gh aw run copilot-auto-issues` from terminal) - -**Important:** This workflow fires whenever one of the watched workflows finishes. -It checks `github.event.workflow_run.conclusion` and only creates issues when -the conclusion is `failure`. Use `github.event.workflow_run.name` to determine -which workflow failed and tailor the response accordingly. - -On trigger, Copilot will automatically: -- Determine which workflow failed (tests, CodeQL, or PyPI publish) -- Gather failure details from artifacts or workflow logs -- Create actionable GitHub issues for each distinct failure -- Assign them to the person whose push caused the failure (`github.actor`) -- Tell Copilot to start working on the fix immediately - -### What Copilot must do - -1. **Gate on failure** (skip if CI passed) - - Check `github.event.workflow_run.conclusion` - - If conclusion is NOT `failure`, skip all steps and log "CI passed — nothing to do" - - On manual trigger → fetch the latest runs for all three watched workflows via the API - -2. **Identify which workflow failed** - - Read `github.event.workflow_run.name` to determine the source: - - `"Run tests"` → test failure path (step 3a) - - `"CodeQL"` → security / code scanning failure path (step 3b) - - `"dynamic"` → PyPI publish failure path (step 3c) - - Identify the commit SHA and branch from `github.event.workflow_run.head_sha` - -3a. **Test failures ("Run tests")** - - Download ALL `test-results-*` artifacts from the triggering workflow run - (one per matrix combination, e.g. `test-results-ubuntu-latest-py3.11`) - - Parse each `test-results.xml` (JUnit XML) to extract: - - Failed test names and their module/file paths - - Error messages and stack traces - - Which OS + Python version the failure occurred on - - Also read each `test-output.txt` for full pytest verbose output - - Note which platforms passed vs failed — a test that only fails on Windows - or macOS is a platform-specific bug and should be labeled accordingly - - Create one issue per distinct failure (or group related failures): - - Labels: `test-failure`, plus `platform-specific` if OS-specific - - Body includes: exact test name(s), error + stack trace, OS/Python matrix, root cause, fix snippet - -3b. **CodeQL / code scanning failures ("CodeQL")** - - Retrieve the SARIF results or workflow logs from the failed run - - Identify which CodeQL query/rule triggered the failure - - Create one issue per distinct security finding: - - Labels: `security` - - Body includes: rule ID, severity, affected file(s) + line(s), explanation, suggested remediation - -3c. **PyPI publish failures ("dynamic")** - - Retrieve the workflow logs to identify the publish error - - Common causes: version conflict, auth failure, build error, missing metadata - - Create one issue: - - Labels: `publish`, `packaging` - - Body includes: full error from the publish step, likely cause, fix steps - -4. **All issue types — common fields** - - Title starts with `[copilot-auto] ` - - Body always includes: - - Triggering commit SHA + link to the failed workflow run - - The source workflow that failed - - The source file(s) likely causing the failure - - Suggested fix with code snippet - - Base labels: `copilot`, `auto-generated`, `bug` - - **Assign to** the pusher/triggerer (`${{ github.actor }}`) - - Add this pinned comment at the top of the issue: - > @copilot Please start working on this immediately. Open a draft PR with the fix when ready. - -5. **If the fix is straightforward** - - Instruct Copilot to implement it and open a linked **draft PR** to the issue. - -6. **Summary** - - If the workflow actually passed (guard) → log "All clear" and exit - - Always link back to the triggering workflow run: `${{ github.event.workflow_run.html_url }}` - diff --git a/.github/workflows/copilot-auto-issues.lock.yml b/.github/workflows/copilot-autofix.lock.yml similarity index 92% rename from .github/workflows/copilot-auto-issues.lock.yml rename to .github/workflows/copilot-autofix.lock.yml index f6fb9c4e..dfe48679 100644 --- a/.github/workflows/copilot-auto-issues.lock.yml +++ b/.github/workflows/copilot-autofix.lock.yml @@ -22,28 +22,26 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"39d497736405f21b5e96b401a9b58b51753ff07080b1b385664a2f336261a05c","compiler_version":"v0.50.4"} +# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"41b89ca7fa5ba95e5da9cfbed8edded5d24b3c440b70cc5324af79c97d5945e4","compiler_version":"v0.50.4"} -name: "Copilot Auto Issues" +name: "Copilot Autofix" "on": workflow_dispatch: workflow_run: # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation branches: - - main + - master types: - completed workflows: - Run tests - - CodeQL - - dynamic permissions: {} concurrency: group: "gh-aw-${{ github.workflow }}" -run-name: "Copilot Auto Issues" +run-name: "Copilot Autofix" jobs: activation: @@ -82,7 +80,7 @@ jobs: - name: Check workflow file timestamps uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - GH_AW_WORKFLOW_FILE: "copilot-auto-issues.lock.yml" + GH_AW_WORKFLOW_FILE: "copilot-autofix.lock.yml" with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -114,7 +112,7 @@ jobs: cat "/opt/gh-aw/prompts/safe_outputs_prompt.md" cat << 'GH_AW_PROMPT_EOF' - Tools: create_issue, missing_tool, missing_data + Tools: add_comment, missing_tool, missing_data The following GitHub context information is available for this workflow: @@ -149,14 +147,13 @@ jobs: GH_AW_PROMPT_EOF cat << 'GH_AW_PROMPT_EOF' - {{#runtime-import .github/workflows/copilot-auto-issues.md}} + {{#runtime-import .github/workflows/copilot-autofix.md}} GH_AW_PROMPT_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_ACTOR: ${{ github.actor }} GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HTML_URL: ${{ github.event.workflow_run.html_url }} with: script: | @@ -223,7 +220,6 @@ jobs: permissions: actions: read contents: read - issues: read pull-requests: read concurrency: group: "gh-aw-copilot-${{ github.workflow }}" @@ -236,7 +232,7 @@ jobs: GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json - GH_AW_WORKFLOW_ID_SANITIZED: copilotautoissues + GH_AW_WORKFLOW_ID_SANITIZED: copilotautofix outputs: checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} @@ -297,7 +293,7 @@ jobs: version: "", agent_version: "0.0.417", cli_version: "v0.50.4", - workflow_name: "Copilot Auto Issues", + workflow_name: "Copilot Autofix", experimental: false, supports_tools_allowlist: true, run_id: context.runId, @@ -354,50 +350,30 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"create_issue":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":10},"missing_data":{},"missing_tool":{},"noop":{"max":1}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF' [ { - "description": "Create a new GitHub issue for tracking bugs, feature requests, or tasks. Use this for actionable work items that need assignment, labeling, and status tracking. For reports, announcements, or status updates that don't require task tracking, use create_discussion instead. CONSTRAINTS: Maximum 10 issue(s) can be created. Title will be prefixed with \"[copilot-auto] \". Labels [copilot auto-generated bug] will be automatically added.", + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 10 comment(s) can be added.", "inputSchema": { "additionalProperties": false, "properties": { "body": { - "description": "Detailed issue description in Markdown. Do NOT repeat the title as a heading since it already appears as the issue's h1. Include context, reproduction steps, or acceptance criteria as appropriate.", + "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.", "type": "string" }, - "labels": { - "description": "Labels to categorize the issue (e.g., 'bug', 'enhancement'). Labels must exist in the repository.", - "items": { - "type": "string" - }, - "type": "array" - }, - "parent": { - "description": "Parent issue number for creating sub-issues. This is the numeric ID from the GitHub URL (e.g., 42 in github.com/owner/repo/issues/42). Can also be a temporary_id (e.g., 'aw_abc123', 'aw_Test123') from a previously created issue in the same workflow run.", - "type": [ - "number", - "string" - ] - }, - "temporary_id": { - "description": "Unique temporary identifier for referencing this issue before it's created. Format: 'aw_' followed by 3 to 8 alphanumeric characters (e.g., 'aw_abc1', 'aw_Test123'). Use '#aw_ID' in body text to reference other issues by their temporary_id; these are replaced with actual issue numbers after creation.", - "pattern": "^aw_[A-Za-z0-9]{3,8}$", - "type": "string" - }, - "title": { - "description": "Concise issue title summarizing the bug, feature, or task. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" + "item_number": { + "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool auto-targets the issue, PR, or discussion that triggered this workflow. Auto-targeting only works for issue, pull_request, discussion, and comment event triggers — it does NOT work for schedule, workflow_dispatch, push, or workflow_run triggers. For those trigger types, always provide item_number explicitly, or the comment will be silently discarded.", + "type": "number" } }, "required": [ - "title", "body" ], "type": "object" }, - "name": "create_issue" + "name": "add_comment" }, { "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.", @@ -472,7 +448,7 @@ jobs: GH_AW_SAFE_OUTPUTS_TOOLS_EOF cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF' { - "create_issue": { + "add_comment": { "defaultMax": 1, "fields": { "body": { @@ -481,27 +457,12 @@ jobs: "sanitize": true, "maxLength": 65000 }, - "labels": { - "type": "array", - "itemType": "string", - "itemSanitize": true, - "itemMaxLength": 128 - }, - "parent": { + "item_number": { "issueOrPRNumber": true }, "repo": { "type": "string", "maxLength": 256 - }, - "temporary_id": { - "type": "string" - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 } } }, @@ -867,7 +828,7 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: - WORKFLOW_NAME: "Copilot Auto Issues" + WORKFLOW_NAME: "Copilot Autofix" WORKFLOW_DESCRIPTION: "No description provided" HAS_PATCH: ${{ steps.collect_output.outputs.has_patch }} with: @@ -955,7 +916,9 @@ jobs: runs-on: self-hosted permissions: contents: read + discussions: write issues: write + pull-requests: write outputs: noop_message: ${{ steps.noop.outputs.noop_message }} tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} @@ -982,7 +945,7 @@ jobs: env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_WORKFLOW_NAME: "Copilot Autofix" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -995,7 +958,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_WORKFLOW_NAME: "Copilot Autofix" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1008,10 +971,10 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_WORKFLOW_NAME: "Copilot Autofix" 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: "copilot-auto-issues" + GH_AW_WORKFLOW_ID: "copilot-autofix" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_GROUP_REPORTS: "false" @@ -1027,7 +990,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_WORKFLOW_NAME: "Copilot Autofix" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }} @@ -1069,12 +1032,14 @@ jobs: runs-on: self-hosted permissions: contents: read + discussions: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_ENGINE_ID: "copilot" - GH_AW_WORKFLOW_ID: "copilot-auto-issues" - GH_AW_WORKFLOW_NAME: "Copilot Auto Issues" + GH_AW_WORKFLOW_ID: "copilot-autofix" + GH_AW_WORKFLOW_NAME: "Copilot Autofix" outputs: 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 }} @@ -1103,7 +1068,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":false,\"labels\":[\"copilot\",\"auto-generated\",\"bug\"],\"max\":10,\"title_prefix\":\"[copilot-auto] \"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/copilot-autofix.md b/.github/workflows/copilot-autofix.md new file mode 100644 index 00000000..ce643450 --- /dev/null +++ b/.github/workflows/copilot-autofix.md @@ -0,0 +1,81 @@ +--- +on: + workflow_run: + workflows: ["Run tests"] + types: [completed] + branches: [master] + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + actions: read + +safe-outputs: + add-comment: + max: 10 +--- +## Auto-Fix Failing Tests on Existing PRs + +**Trigger:** Fires when the "Run tests" workflow completes (on any branch). + +If the conclusion is `failure` AND the run is associated with a pull request, +Copilot should analyze the failures and post fix suggestions **directly on +the existing PR** — no new PR, no new issue. + +If the conclusion is `success`, do nothing and exit. + +### Instructions for Copilot + +1. **Gate on failure** + - Check `github.event.workflow_run.conclusion` + - If NOT `failure` → exit immediately, log "Tests passed — nothing to fix" + +2. **Find the associated PR** + - Check `github.event.workflow_run.pull_requests` for the PR(s) linked to this run + - If no PR is associated (e.g. a direct push to main), exit — this workflow + only handles PRs + - Note the PR number, branch name, and head SHA + +3. **Get failure details** + - Download ALL `test-results-*` artifacts from the triggering run + - Parse each `test-results.xml` (JUnit XML) to find: + - Which tests failed (full `module::class::test` path) + - Error messages and stack traces + - Which OS + Python version failed + - Read `test-output.txt` for additional context + - Get the commit SHA from `github.event.workflow_run.head_sha` + +4. **Analyze the failures** + - Checkout the repo at the failing commit on the PR branch + - For each failing test, trace the error back to the source code + - Determine the root cause — is it a bug in the source, a test that needs + updating, a missing dependency, or a platform-specific issue? + - Identify the exact file(s) and line(s) that need to change + +5. **Post fix suggestions on the existing PR** + - Add a comment on the PR with a summary: + - Link to the failed workflow run: `${{ github.event.workflow_run.html_url }}` + - How many tests failed and on which platforms + - Brief root cause for each failure group + - For each fix, use GitHub suggestion blocks so the author can accept with + one click. Format each suggestion like: + + ```` + ```suggestion + corrected code here + ``` + ```` + + - If multiple tests fail for the same root cause, group them into one suggestion + - If a fix spans multiple files or is too complex for a suggestion block, + describe the fix clearly in the comment with code snippets and file paths + - Always explain WHY the change fixes the test + +6. **Scope rules** + - Fix ONLY what's needed to make the failing tests pass + - Do not suggest refactoring unrelated code + - Do not weaken test assertions to make them pass — fix the source + - If the fix is unclear or risky, say so in the comment + - If there are merge conflicts (like `clarifai/__init__.py`), note them but + do not attempt to resolve — just mention the conflicts need manual resolution