Analyze CI Failure #147
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c44db4d6b9d3eb5fcff0743f5be77375e66dec9b1c263c8d33deea9dcb257240","body_hash":"e9f3545ccbe75e728bdac1140123f0ebea921e69ff4b3a44a28fa977bf00ba74","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} | |
| # gh-aw-manifest: {"version":1,"secrets":["GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"34e114876b0b11c390a56381ad16ebd13914f8d5","version":"v4"},{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/download-artifact","sha":"d3f86a106a0bac45b974a628896c90dbdf5c8093","version":"v4"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"actions/upload-artifact","sha":"ea165f8d65b6e75b540449e92b4886f43607fa02","version":"v4"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} | |
| # This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md | |
| # | |
| # ___ _ _ | |
| # / _ \ | | (_) | |
| # | |_| | __ _ ___ _ __ | |_ _ ___ | |
| # | _ |/ _` |/ _ \ '_ \| __| |/ __| | |
| # | | | | (_| | __/ | | | |_| | (__ | |
| # \_| |_/\__, |\___|_| |_|\__|_|\___| | |
| # __/ | | |
| # _ _ |___/ | |
| # | | | | / _| | | |
| # | | | | ___ _ __ _ __| |_| | _____ ____ | |
| # | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| | |
| # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ | |
| # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ | |
| # | |
| # | |
| # To update this file, edit the corresponding .md file and run: | |
| # gh aw compile | |
| # Not all edits will cause changes to this file. | |
| # | |
| # For more information: https://github.github.com/gh-aw/introduction/overview/ | |
| # | |
| # Analyzes failed PR CI builds using Copilot to determine whether the failure | |
| # is transient (flaky test, infrastructure issue) or caused by the PR changes | |
| # (compilation error, test regression). For transient infrastructure failures, | |
| # reruns the CI build. For transient test failures, posts a comment with | |
| # details and suggested next steps. For non-transient failures, posts a | |
| # comment explaining the root cause. | |
| # | |
| # Frontmatter env variables: | |
| # - ENABLE_RERUN: (main workflow) | |
| # | |
| # Secrets used: | |
| # - GH_AW_GITHUB_MCP_SERVER_TOKEN | |
| # - GH_AW_GITHUB_TOKEN | |
| # - GITHUB_TOKEN | |
| # | |
| # Custom actions used: | |
| # - actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| # - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| # - actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| # - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| # - actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| # - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| # | |
| # Container images used: | |
| # - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 | |
| # - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 | |
| # - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 | |
| # - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa | |
| # - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c | |
| name: "Analyze CI Failure" | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| aw_context: | |
| default: "" | |
| description: "Agent caller context (used internally by Agentic Workflows)." | |
| required: false | |
| type: string | |
| run_id: | |
| description: CI workflow run ID to analyze | |
| required: true | |
| type: number | |
| workflow_run: | |
| # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation | |
| branches: | |
| - main | |
| types: | |
| - completed | |
| workflows: | |
| - CI | |
| permissions: {} | |
| concurrency: | |
| cancel-in-progress: false | |
| group: analyze-ci-failure-${{ github.event_name == 'workflow_dispatch' && inputs.run_id || github.event.workflow_run.id }} | |
| run-name: "Analyze CI Failure" | |
| env: | |
| ENABLE_RERUN: "false" | |
| jobs: | |
| activation: | |
| needs: pre_activation | |
| # 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: ubuntu-slim | |
| permissions: | |
| actions: read | |
| contents: read | |
| env: | |
| GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} | |
| outputs: | |
| comment_id: "" | |
| comment_repo: "" | |
| daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} | |
| daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} | |
| daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} | |
| engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} | |
| lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} | |
| model: ${{ steps.generate_aw_info.outputs.model }} | |
| setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} | |
| setup-span-id: ${{ steps.setup.outputs.span-id }} | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} | |
| parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} | |
| safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Generate agentic run info | |
| id: generate_aw_info | |
| env: | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" | |
| GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AGENT_VERSION: "1.0.60" | |
| GH_AW_INFO_CLI_VERSION: "v0.79.8" | |
| GH_AW_INFO_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_INFO_EXPERIMENTAL: "false" | |
| GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" | |
| GH_AW_INFO_STAGED: "false" | |
| GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github"]' | |
| GH_AW_INFO_FIREWALL_ENABLED: "true" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_AWMG_VERSION: "" | |
| GH_AW_INFO_FIREWALL_TYPE: "squid" | |
| GH_AW_COMPILED_STRICT: "true" | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); | |
| await main(core, context); | |
| - name: Check daily workflow token guardrail | |
| id: daily-effective-workflow-guardrail | |
| if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_ID: "analyze-ci-failure" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} | |
| GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); | |
| await main(); | |
| - name: Checkout .github and .agents folders | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: | | |
| .github | |
| .agents | |
| .antigravity | |
| .claude | |
| .codex | |
| .crush | |
| .gemini | |
| .opencode | |
| .pi | |
| sparse-checkout-cone-mode: true | |
| fetch-depth: 1 | |
| - name: Save agent config folders for base branch restoration | |
| env: | |
| GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" | |
| GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" | |
| - name: Check workflow lock file | |
| id: check-lock-file | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_WORKFLOW_FILE: "analyze-ci-failure.lock.yml" | |
| GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); | |
| await main(); | |
| - name: Check compile-agentic version | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_COMPILED_VERSION: "v0.79.8" | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); | |
| await main(); | |
| - name: Create prompt with built-in context | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl | |
| GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} | |
| GH_AW_GITHUB_ACTOR: ${{ github.actor }} | |
| GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} | |
| GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} | |
| GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} | |
| # poutine:ignore untrusted_checkout_exec | |
| run: | | |
| bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" | |
| { | |
| cat << 'GH_AW_PROMPT_ba22f0ee2d94aac2_EOF' | |
| <system> | |
| GH_AW_PROMPT_ba22f0ee2d94aac2_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" | |
| cat << 'GH_AW_PROMPT_ba22f0ee2d94aac2_EOF' | |
| <safe-output-tools> | |
| Tools: missing_tool, missing_data, noop, publish_data, rerun_failed_jobs | |
| </safe-output-tools> | |
| GH_AW_PROMPT_ba22f0ee2d94aac2_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" | |
| cat << 'GH_AW_PROMPT_ba22f0ee2d94aac2_EOF' | |
| <github-context> | |
| The following GitHub context information is available for this workflow: | |
| {{#if github.actor}} | |
| - **actor**: __GH_AW_GITHUB_ACTOR__ | |
| {{/if}} | |
| {{#if github.repository}} | |
| - **repository**: __GH_AW_GITHUB_REPOSITORY__ | |
| {{/if}} | |
| {{#if github.workspace}} | |
| - **workspace**: __GH_AW_GITHUB_WORKSPACE__ | |
| {{/if}} | |
| {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} | |
| - **issue-number**: #__GH_AW_EXPR_802A9F6A__ | |
| {{/if}} | |
| {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} | |
| - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ | |
| {{/if}} | |
| {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} | |
| - **pull-request-number**: #__GH_AW_EXPR_463A214A__ | |
| {{/if}} | |
| {{#if github.event.comment.id || github.aw.context.comment_id}} | |
| - **comment-id**: __GH_AW_EXPR_FF1D34CE__ | |
| {{/if}} | |
| {{#if github.run_id}} | |
| - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ | |
| {{/if}} | |
| </github-context> | |
| GH_AW_PROMPT_ba22f0ee2d94aac2_EOF | |
| cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" | |
| cat << 'GH_AW_PROMPT_ba22f0ee2d94aac2_EOF' | |
| </system> | |
| {{#runtime-import .github/workflows/analyze-ci-failure.md}} | |
| GH_AW_PROMPT_ba22f0ee2d94aac2_EOF | |
| } > "$GH_AW_PROMPT" | |
| - name: Interpolate variables and render templates | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_ENGINE_ID: "copilot" | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); | |
| await main(); | |
| - name: Substitute placeholders | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} | |
| GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} | |
| GH_AW_GITHUB_ACTOR: ${{ github.actor }} | |
| GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} | |
| GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} | |
| GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' | |
| GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); | |
| // Call the substitution function | |
| return await substitutePlaceholders({ | |
| file: process.env.GH_AW_PROMPT, | |
| substitutions: { | |
| GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, | |
| GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, | |
| GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, | |
| GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, | |
| GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, | |
| GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, | |
| GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, | |
| GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, | |
| GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, | |
| GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED | |
| } | |
| }); | |
| - name: Validate prompt placeholders | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" | |
| - name: Print prompt | |
| env: | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| # poutine:ignore untrusted_checkout_exec | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" | |
| - name: Upload activation artifact | |
| if: success() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: activation | |
| include-hidden-files: true | |
| path: | | |
| /tmp/gh-aw/aw_info.json | |
| /tmp/gh-aw/models.json | |
| /tmp/gh-aw/aw-prompts/prompt.txt | |
| /tmp/gh-aw/aw-prompts/prompt-template.txt | |
| /tmp/gh-aw/aw-prompts/prompt-import-tree.json | |
| /tmp/gh-aw/github_rate_limits.jsonl | |
| /tmp/gh-aw/base | |
| /tmp/gh-aw/.github/agents | |
| /tmp/gh-aw/.github/skills | |
| if-no-files-found: ignore | |
| retention-days: 1 | |
| agent: | |
| needs: | |
| - activation | |
| - collect-data | |
| if: (needs.collect-data.outputs.has-work == 'true') && (needs.activation.outputs.daily_ai_credits_exceeded != 'true') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| checks: read | |
| contents: read | |
| copilot-requests: write | |
| issues: read | |
| pull-requests: read | |
| concurrency: | |
| group: "gh-aw-copilot-${{ github.workflow }}" | |
| queue: max | |
| 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_WORKFLOW_ID_SANITIZED: analyzecifailure | |
| outputs: | |
| agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} | |
| ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} | |
| aic: ${{ steps.parse-mcp-gateway.outputs.aic }} | |
| ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} | |
| checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} | |
| effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} | |
| has_patch: ${{ steps.collect_output.outputs.has_patch }} | |
| inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} | |
| mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} | |
| model: ${{ needs.activation.outputs.model }} | |
| model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} | |
| output: ${{ steps.collect_output.outputs.output }} | |
| output_types: ${{ steps.collect_output.outputs.output_types }} | |
| setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} | |
| setup-span-id: ${{ steps.setup.outputs.span-id }} | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Set runtime paths | |
| id: set-runtime-paths | |
| run: | | |
| { | |
| echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" | |
| echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" | |
| echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Checkout repository | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Create gh-aw temp directory | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" | |
| - name: Configure gh CLI for GitHub Enterprise | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: ci-failure-data | |
| path: ci-failure-data/ | |
| - name: Configure Git credentials | |
| env: | |
| REPO_NAME: ${{ github.repository }} | |
| SERVER_URL: ${{ github.server_url }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| 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 || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); | |
| await main(); | |
| - name: Install GitHub Copilot CLI | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 | |
| env: | |
| GH_HOST: github.com | |
| - name: Install AWF binary | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 | |
| - name: Determine automatic lockdown mode for GitHub MCP Server | |
| id: determine-automatic-lockdown | |
| uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| 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('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs'); | |
| await determineAutomaticLockdown(github, context, core); | |
| - name: Download activation artifact | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: activation | |
| path: /tmp/gh-aw | |
| - name: Restore agent config folders from base branch | |
| if: steps.checkout-pr.outcome == 'success' | |
| env: | |
| GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" | |
| GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" | |
| - name: Restore inline sub-agents from activation artifact | |
| env: | |
| GH_AW_SUB_AGENT_DIR: ".github/agents" | |
| GH_AW_SUB_AGENT_EXT: ".agent.md" | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" | |
| - name: Restore inline skills from activation artifact | |
| env: | |
| GH_AW_SKILL_DIR: ".github/skills" | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" | |
| - name: Download container images | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c | |
| - name: Generate Safe Outputs Config | |
| run: | | |
| mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" | |
| mkdir -p /tmp/gh-aw/safeoutputs | |
| mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs | |
| cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_6fc0db214c0c0e17_EOF' | |
| {"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"publish-data":{"description":"Publishes the CI failure analysis to the memory branch and posts a PR\ncomment. The agent must write:\n - /tmp/gh-aw/agent/analysis-result.json (run summary)\n - /tmp/gh-aw/agent/causes/*.json (one file per failure cause)\nEmit exactly one `publish_data` item with run_id and pr_numbers.\n","inputs":{"pr_numbers":{"default":null,"description":"Comma-separated list of associated PR numbers.","required":true,"type":"string"},"run_id":{"default":null,"description":"The workflow run ID that was analyzed.","required":true,"type":"number"}}},"report_incomplete":{},"rerun-failed-jobs":{"description":"Reruns the failed CI jobs when the agent determines all failures are\ntransient infrastructure issues. Emit exactly one `rerun_failed_jobs`\nitem with the run_id and pr_numbers when a rerun is warranted.\n","inputs":{"pr_numbers":{"default":null,"description":"Comma-separated list of associated PR numbers.","required":true,"type":"string"},"reason":{"default":null,"description":"Short summary of why the rerun was requested.","required":true,"type":"string"},"run_id":{"default":null,"description":"The workflow run ID to rerun failed jobs for.","required":true,"type":"number"}}}} | |
| GH_AW_SAFE_OUTPUTS_CONFIG_6fc0db214c0c0e17_EOF | |
| - name: Generate Safe Outputs Tools | |
| env: | |
| GH_AW_TOOLS_META_JSON: | | |
| { | |
| "description_suffixes": {}, | |
| "repo_params": {}, | |
| "dynamic_tools": [ | |
| { | |
| "description": "Publishes the CI failure analysis to the memory branch and posts a PR\ncomment. The agent must write:\n - /tmp/gh-aw/agent/analysis-result.json (run summary)\n - /tmp/gh-aw/agent/causes/*.json (one file per failure cause)\nEmit exactly one `publish_data` item with run_id and pr_numbers.\n", | |
| "inputSchema": { | |
| "additionalProperties": false, | |
| "properties": { | |
| "pr_numbers": { | |
| "description": "Comma-separated list of associated PR numbers.", | |
| "type": "string" | |
| }, | |
| "run_id": { | |
| "description": "The workflow run ID that was analyzed.", | |
| "type": "number" | |
| } | |
| }, | |
| "required": [ | |
| "pr_numbers", | |
| "run_id" | |
| ], | |
| "type": "object" | |
| }, | |
| "name": "publish_data" | |
| }, | |
| { | |
| "description": "Reruns the failed CI jobs when the agent determines all failures are\ntransient infrastructure issues. Emit exactly one `rerun_failed_jobs`\nitem with the run_id and pr_numbers when a rerun is warranted.\n", | |
| "inputSchema": { | |
| "additionalProperties": false, | |
| "properties": { | |
| "pr_numbers": { | |
| "description": "Comma-separated list of associated PR numbers.", | |
| "type": "string" | |
| }, | |
| "reason": { | |
| "description": "Short summary of why the rerun was requested.", | |
| "type": "string" | |
| }, | |
| "run_id": { | |
| "description": "The workflow run ID to rerun failed jobs for.", | |
| "type": "number" | |
| } | |
| }, | |
| "required": [ | |
| "pr_numbers", | |
| "reason", | |
| "run_id" | |
| ], | |
| "type": "object" | |
| }, | |
| "name": "rerun_failed_jobs" | |
| } | |
| ] | |
| } | |
| GH_AW_VALIDATION_JSON: | | |
| { | |
| "missing_data": { | |
| "defaultMax": 20, | |
| "fields": { | |
| "alternatives": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "context": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "data_type": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| }, | |
| "reason": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| } | |
| } | |
| }, | |
| "missing_tool": { | |
| "defaultMax": 20, | |
| "fields": { | |
| "alternatives": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 512 | |
| }, | |
| "reason": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 256 | |
| }, | |
| "tool": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 128 | |
| } | |
| } | |
| }, | |
| "noop": { | |
| "defaultMax": 1, | |
| "fields": { | |
| "message": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 65000 | |
| } | |
| } | |
| }, | |
| "report_incomplete": { | |
| "defaultMax": 5, | |
| "fields": { | |
| "details": { | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 65000 | |
| }, | |
| "reason": { | |
| "required": true, | |
| "type": "string", | |
| "sanitize": true, | |
| "maxLength": 1024 | |
| } | |
| } | |
| } | |
| } | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); | |
| await main(); | |
| - name: 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: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| 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: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json | |
| GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/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 | |
| 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 "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" | |
| - name: Start MCP Gateway | |
| id: start-mcp-gateway | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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_GUARD_MIN_INTEGRITY: ${{ steps.determine-automatic-lockdown.outputs.min_integrity }} | |
| GITHUB_MCP_GUARD_REPOS: ${{ steps.determine-automatic-lockdown.outputs.repos }} | |
| 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 "${RUNNER_TEMP}/gh-aw/mcp-config" | |
| # Export gateway environment variables for MCP config and gateway script | |
| export MCP_GATEWAY_PORT="8080" | |
| export MCP_GATEWAY_DOMAIN="host.docker.internal" | |
| export MCP_GATEWAY_HOST_DOMAIN="localhost" | |
| MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') | |
| echo "::add-mask::${MCP_GATEWAY_API_KEY}" | |
| export MCP_GATEWAY_API_KEY | |
| export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" | |
| mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" | |
| export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" | |
| export DEBUG="*" | |
| export GH_AW_ENGINE="copilot" | |
| MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') | |
| MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') | |
| case "${DOCKER_HOST:-}" in | |
| unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; | |
| /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; | |
| * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; | |
| esac | |
| DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') | |
| export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e 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.3.25' | |
| mkdir -p "$HOME/.copilot" | |
| GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) | |
| cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" | |
| { | |
| "mcpServers": { | |
| "github": { | |
| "type": "stdio", | |
| "container": "ghcr.io/github/github-mcp-server:v1.1.2", | |
| "env": { | |
| "GITHUB_HOST": "\${GITHUB_SERVER_URL}", | |
| "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", | |
| "GITHUB_READ_ONLY": "1", | |
| "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" | |
| }, | |
| "guard-policies": { | |
| "allow-only": { | |
| "min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY", | |
| "repos": "$GITHUB_MCP_GUARD_REPOS" | |
| } | |
| } | |
| }, | |
| "safeoutputs": { | |
| "type": "http", | |
| "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", | |
| "headers": { | |
| "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" | |
| }, | |
| "guard-policies": { | |
| "write-sink": { | |
| "accept": [ | |
| "*" | |
| ] | |
| } | |
| } | |
| } | |
| }, | |
| "gateway": { | |
| "port": $MCP_GATEWAY_PORT, | |
| "domain": "${MCP_GATEWAY_DOMAIN}", | |
| "apiKey": "${MCP_GATEWAY_API_KEY}", | |
| "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" | |
| } | |
| } | |
| GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | |
| - name: Mount MCP servers as CLIs | |
| id: mount-mcp-clis | |
| continue-on-error: true | |
| env: | |
| MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} | |
| MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} | |
| MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); | |
| await main(); | |
| - name: Clean credentials | |
| continue-on-error: true | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" | |
| - name: Audit pre-agent workspace | |
| id: pre_agent_audit | |
| continue-on-error: true | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" | |
| - name: Execute GitHub Copilot CLI | |
| id: agentic_execution | |
| # Copilot CLI tool arguments (sorted): | |
| timeout-minutes: 20 | |
| run: | | |
| set -o pipefail | |
| printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt | |
| trap 'rm -f "$HOME/.copilot/settings.json"' EXIT | |
| mkdir -p "$HOME/.copilot" | |
| printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" | |
| export XDG_CONFIG_HOME="$HOME" | |
| export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" | |
| touch /tmp/gh-aw/agent-step-summary.md | |
| GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) | |
| export GH_AW_NODE_BIN | |
| export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" | |
| (umask 177 && touch /tmp/gh-aw/agent-stdio.log) | |
| GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" | |
| printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"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\",\"codeload.github.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\",\"docs.github.com\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"objects.githubusercontent.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\",\"patch-diff.githubusercontent.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" | |
| cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json | |
| export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" | |
| GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" | |
| if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then | |
| GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" | |
| fi | |
| GH_AW_TOOL_CACHE_MOUNT="" | |
| GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" | |
| if [ -d "$GH_AW_TOOL_CACHE" ]; then | |
| if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then | |
| GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" | |
| fi | |
| elif [ -d "/home/runner/work/_tool" ]; then | |
| GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" | |
| fi | |
| # shellcheck disable=SC1003 | |
| sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ | |
| -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log | |
| env: | |
| AWF_REFLECT_ENABLED: 1 | |
| COPILOT_AGENT_RUNNER_TYPE: STANDALONE | |
| COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} | |
| GH_AW_PHASE: agent | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_TIMEOUT_MINUTES: 20 | |
| GH_AW_VERSION: v0.79.8 | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GITHUB_AW: true | |
| GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows | |
| GITHUB_HEAD_REF: ${{ github.head_ref }} | |
| GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md | |
| GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| RUNNER_TEMP: ${{ runner.temp }} | |
| S2STOKENS: true | |
| - name: Detect agent errors | |
| if: always() | |
| id: detect-agent-errors | |
| continue-on-error: true | |
| run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" | |
| - name: Configure Git credentials | |
| env: | |
| REPO_NAME: ${{ github.repository }} | |
| SERVER_URL: ${{ github.server_url }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| 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: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" | |
| - name: Stop MCP Gateway | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} | |
| MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} | |
| GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} | |
| run: | | |
| bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" | |
| - name: Redact secrets in logs | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); | |
| await main(); | |
| env: | |
| GH_AW_SECRET_NAMES: 'GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' | |
| SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} | |
| SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} | |
| SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Append agent step summary | |
| if: always() | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" | |
| - name: Copy Safe Outputs | |
| if: always() | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| run: | | |
| mkdir -p /tmp/gh-aw | |
| cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true | |
| - name: Ingest agent output | |
| id: collect_output | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} | |
| GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,codeload.github.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,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.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,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); | |
| await main(); | |
| - name: Parse agent logs for step summary | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); | |
| await main(); | |
| - name: Parse MCP Gateway logs for step summary | |
| if: always() | |
| id: parse-mcp-gateway | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); | |
| await main(); | |
| - name: Print firewall logs | |
| if: always() | |
| continue-on-error: true | |
| env: | |
| AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs | |
| run: | | |
| # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts | |
| # AWF runs with sudo, creating files owned by root | |
| sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 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: Parse token usage for step summary | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); | |
| await main(); | |
| - name: Print AWF reflect summary | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); | |
| await main(); | |
| - name: Write agent output placeholder if missing | |
| if: always() | |
| run: | | |
| if [ ! -f /tmp/gh-aw/agent_output.json ]; then | |
| echo '{"items":[]}' > /tmp/gh-aw/agent_output.json | |
| fi | |
| - name: Upload agent artifacts | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: agent | |
| path: | | |
| /tmp/gh-aw/aw-prompts/prompt.txt | |
| /tmp/gh-aw/sandbox/agent/logs/ | |
| /tmp/gh-aw/redacted-urls.log | |
| /tmp/gh-aw/mcp-logs/ | |
| /tmp/gh-aw/agent_usage.json | |
| /tmp/gh-aw/agent-stdio.log | |
| /tmp/gh-aw/pre-agent-audit.txt | |
| /tmp/gh-aw/agent/ | |
| /tmp/gh-aw/github_rate_limits.jsonl | |
| /tmp/gh-aw/safeoutputs.jsonl | |
| /tmp/gh-aw/agent_output.json | |
| /tmp/gh-aw/aw-*.patch | |
| /tmp/gh-aw/aw-*.bundle | |
| /tmp/gh-aw/awf-config.json | |
| /tmp/gh-aw/sandbox/firewall/logs/ | |
| /tmp/gh-aw/sandbox/firewall/audit/ | |
| /tmp/gh-aw/sandbox/firewall/awf-reflect.json | |
| if-no-files-found: ignore | |
| collect-data: | |
| needs: activation | |
| if: > | |
| github.repository_owner == 'microsoft' && ( | |
| github.event_name == 'workflow_dispatch' | |
| || ( | |
| github.event.workflow_run.conclusion == 'failure' | |
| && github.event.workflow_run.run_attempt <= 3 | |
| ) | |
| ) | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: read | |
| checks: read | |
| contents: read | |
| pull-requests: read | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| outputs: | |
| has-work: ${{ steps.collect.outputs.has_work }} | |
| pr_numbers: ${{ steps.collect.outputs.pr_numbers }} | |
| run_attempt: ${{ steps.collect.outputs.run_attempt }} | |
| run_id: ${{ steps.collect.outputs.run_id }} | |
| run_url: ${{ steps.collect.outputs.run_url }} | |
| steps: | |
| - name: Configure GH_HOST for enterprise compatibility | |
| id: ghes-host-config | |
| shell: bash | |
| # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. | |
| run: | | |
| # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct | |
| # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. | |
| GH_HOST="${GITHUB_SERVER_URL#https://}" | |
| GH_HOST="${GH_HOST#http://}" | |
| echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" | |
| - name: Checkout (for retry patterns) | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| sparse-checkout: eng/test-retry-patterns.json | |
| sparse-checkout-cone-mode: false | |
| - name: Collect CI failure data | |
| id: collect | |
| run: | | |
| set -euo pipefail | |
| mkdir -p ci-failure-data | |
| # Resolve the run ID | |
| if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then | |
| RUN_ID="${MANUAL_RUN_ID}" | |
| else | |
| RUN_ID="${WORKFLOW_RUN_ID}" | |
| fi | |
| echo "Analyzing CI run: ${RUN_ID}" | |
| echo "run_id=${RUN_ID}" >> "$GITHUB_OUTPUT" | |
| # Fetch the workflow run metadata | |
| gh api "repos/${REPO}/actions/runs/${RUN_ID}" > ci-failure-data/run.json | |
| RUN_ATTEMPT=$(jq -r '.run_attempt // 1' ci-failure-data/run.json) | |
| HEAD_SHA=$(jq -r '.head_sha // ""' ci-failure-data/run.json) | |
| HEAD_BRANCH=$(jq -r '.head_branch // ""' ci-failure-data/run.json) | |
| RUN_URL=$(jq -r '.html_url // ""' ci-failure-data/run.json) | |
| CONCLUSION=$(jq -r '.conclusion // ""' ci-failure-data/run.json) | |
| echo "run_attempt=${RUN_ATTEMPT}" >> "$GITHUB_OUTPUT" | |
| echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT" | |
| echo "run_url=${RUN_URL}" >> "$GITHUB_OUTPUT" | |
| # Skip analysis if the run succeeded (e.g. manual dispatch on a passing run) | |
| if [ "${CONCLUSION}" = "success" ]; then | |
| echo "Run concluded with success. Nothing to analyze." | |
| echo "has_work=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| # Find the associated PR number | |
| PR_NUMBERS=$(jq -r '[.pull_requests[]?.number] | join(",")' ci-failure-data/run.json) | |
| if [ -z "${PR_NUMBERS}" ]; then | |
| # Fallback 1: search for PRs by head branch (requires owner:branch format) | |
| HEAD_OWNER=$(jq -r '.head_repository.owner.login // ""' ci-failure-data/run.json) | |
| if [ -n "${HEAD_OWNER}" ] && [ -n "${HEAD_BRANCH}" ]; then | |
| PR_NUMBERS=$(gh api "repos/${REPO}/pulls?state=open&head=${HEAD_OWNER}:${HEAD_BRANCH}" \ | |
| --jq '[.[].number] | join(",")' 2>/dev/null || echo "") | |
| fi | |
| fi | |
| if [ -z "${PR_NUMBERS}" ]; then | |
| # Fallback 2: find PRs associated with the head commit SHA. | |
| # This works even when the PR is merged/closed or the run metadata | |
| # doesn't include the pull_requests array. | |
| if [ -n "${HEAD_SHA}" ]; then | |
| PR_NUMBERS=$(gh api "repos/${REPO}/commits/${HEAD_SHA}/pulls" \ | |
| --jq '[.[].number] | join(",")' 2>/dev/null || echo "") | |
| fi | |
| fi | |
| echo "pr_numbers=${PR_NUMBERS}" >> "$GITHUB_OUTPUT" | |
| if [ -z "${PR_NUMBERS}" ]; then | |
| echo "No associated PR found. Analysis will proceed without PR context." | |
| fi | |
| # Fetch all jobs for this run attempt. | |
| # Use --jq '.jobs[]' to emit individual job objects (handles pagination | |
| # correctly) then jq -s collects them into a single JSON array. | |
| gh api --paginate "repos/${REPO}/actions/runs/${RUN_ID}/attempts/${RUN_ATTEMPT}/jobs" \ | |
| --jq '.jobs[]' | jq -s '.' > ci-failure-data/all-jobs.json | |
| # Extract failed jobs, excluding "gate" jobs that just check dependency status. | |
| # Gate jobs (e.g. "Final Results", "Final Test Results") only echo "dependent jobs | |
| # failed" and provide zero diagnostic value — they just inflate the logs. | |
| jq '[.[] | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out") | |
| | select( | |
| (.steps // [] | map(select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out")) | length) > 0 | |
| and ( | |
| (.steps // [] | map(select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out")) | .[0].name) | |
| | test("^(Fail if|Check ).*(depend|failed)"; "i") | not | |
| ) | |
| )]' \ | |
| ci-failure-data/all-jobs.json > ci-failure-data/failed-jobs.json | |
| FAILED_COUNT=$(jq 'length' ci-failure-data/failed-jobs.json) | |
| echo "Failed jobs: ${FAILED_COUNT}" | |
| if [ "${FAILED_COUNT}" -eq 0 ]; then | |
| echo "No failed jobs found. Skipping analysis." | |
| echo "has_work=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "has_work=true" >> "$GITHUB_OUTPUT" | |
| # Fetch logs for each failed job and extract only error-relevant lines. | |
| # Raw logs are huge (64KB+). Instead of blindly taking the last N lines, | |
| # we grep for error indicators with context to produce a focused extract. | |
| jq -r '.[].id' ci-failure-data/failed-jobs.json | while read -r JOB_ID; do | |
| JOB_NAME=$(jq -r ".[] | select(.id == ${JOB_ID}) | .name" ci-failure-data/failed-jobs.json) | |
| echo "Fetching logs for job: ${JOB_NAME} (${JOB_ID})" | |
| gh api "repos/${REPO}/actions/jobs/${JOB_ID}/logs" > "ci-failure-data/job-${JOB_ID}-raw.log" 2>/dev/null || \ | |
| echo "(Failed to fetch logs for job ${JOB_ID})" > "ci-failure-data/job-${JOB_ID}-raw.log" | |
| # Extract error-relevant lines with 3 lines of context before and 5 after. | |
| # Patterns: compiler errors, build failures, test failures, runtime errors, | |
| # infrastructure errors, and GitHub Actions error annotations. | |
| grep -n -i -B3 -A5 \ | |
| -e 'error [A-Z]\{2,\}[0-9]' \ | |
| -e '##\[error\]' \ | |
| -e '\bFAILED\b' \ | |
| -e '\bfailed!\b' \ | |
| -e 'Build FAILED' \ | |
| -e 'ECONNRESET\|ECONNREFUSED\|ENOTFOUND' \ | |
| -e 'Connection reset by peer' \ | |
| -e 'Could not resolve host' \ | |
| -e 'Operation timed out' \ | |
| -e 'The SSL connection could not be established' \ | |
| -e '403 Forbidden' \ | |
| -e 'exit code [1-9]' \ | |
| -e 'Process completed with exit code' \ | |
| "ci-failure-data/job-${JOB_ID}-raw.log" 2>/dev/null \ | |
| | head -150 > "ci-failure-data/job-${JOB_ID}.log" || true | |
| # If grep found nothing, fall back to last 200 lines (job may have unusual errors) | |
| if [ ! -s "ci-failure-data/job-${JOB_ID}.log" ]; then | |
| tail -200 "ci-failure-data/job-${JOB_ID}-raw.log" > "ci-failure-data/job-${JOB_ID}.log" | |
| fi | |
| rm -f "ci-failure-data/job-${JOB_ID}-raw.log" | |
| done | |
| # Fetch annotations for each failed job | |
| jq -r '.[].id' ci-failure-data/failed-jobs.json | while read -r JOB_ID; do | |
| CHECK_RUN_ID=$(jq -r ".[] | select(.id == ${JOB_ID}) | .check_run_url" ci-failure-data/failed-jobs.json \ | |
| | grep -oP '\d+$' || echo "") | |
| if [ -n "${CHECK_RUN_ID}" ]; then | |
| gh api --paginate "repos/${REPO}/check-runs/${CHECK_RUN_ID}/annotations" \ | |
| > "ci-failure-data/annotations-${JOB_ID}.json" 2>/dev/null || \ | |
| echo "[]" > "ci-failure-data/annotations-${JOB_ID}.json" | |
| else | |
| echo "[]" > "ci-failure-data/annotations-${JOB_ID}.json" | |
| fi | |
| done | |
| # Fetch the PR diff to compare against failures | |
| FIRST_PR=$(echo "${PR_NUMBERS}" | cut -d',' -f1) | |
| if [ -n "${FIRST_PR}" ]; then | |
| gh api "repos/${REPO}/pulls/${FIRST_PR}/files" --paginate \ | |
| --jq '.[]' | jq -s '[.[] | {filename, status, additions, deletions, changes}]' \ | |
| > ci-failure-data/pr-files.json 2>/dev/null || echo "[]" > ci-failure-data/pr-files.json | |
| # Fetch PR metadata (state, title, author) so the agent doesn't need | |
| # to make MCP pull_request_read calls at runtime. | |
| gh api "repos/${REPO}/pulls/${FIRST_PR}" \ | |
| --jq '{number, title, state, user: .user.login, head_branch: .head.ref, base_branch: .base.ref, html_url}' \ | |
| > ci-failure-data/pr-metadata.json 2>/dev/null || echo "{}" > ci-failure-data/pr-metadata.json | |
| fi | |
| # Load the known transient failure patterns for reference | |
| if [ -f "eng/test-retry-patterns.json" ]; then | |
| cp eng/test-retry-patterns.json ci-failure-data/retry-patterns.json | |
| fi | |
| # Fetch prior cause files from the memory branch so the agent can | |
| # identify recurring failures and append occurrences rather than | |
| # creating duplicate cause entries. | |
| MEMORY_BRANCH="memory/ci-failure-analysis" | |
| if git clone --depth 1 --branch "$MEMORY_BRANCH" \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" \ | |
| memory-checkout 2>/dev/null; then | |
| if [ -d "memory-checkout/causes" ]; then | |
| mkdir -p ci-failure-data/prior-causes | |
| cp memory-checkout/causes/*.json ci-failure-data/prior-causes/ 2>/dev/null || true | |
| PRIOR_COUNT=$(find ci-failure-data/prior-causes -name '*.json' -type f 2>/dev/null | wc -l) | |
| echo "Loaded ${PRIOR_COUNT} prior cause file(s) from memory branch" | |
| else | |
| echo "No prior causes directory on memory branch" | |
| fi | |
| rm -rf memory-checkout | |
| else | |
| echo "Memory branch not found (first run or not yet created)" | |
| fi | |
| # Fetch test results artifact if available and extract test failure info | |
| ARTIFACT_NAME=$(gh api "repos/${REPO}/actions/runs/${RUN_ID}/artifacts" \ | |
| --jq '[.artifacts[] | select(.name | test("test-results|TestResults"; "i"))] | first | .name // empty' 2>/dev/null || echo "") | |
| if [ -n "${ARTIFACT_NAME}" ]; then | |
| echo "Downloading test results artifact: ${ARTIFACT_NAME}..." | |
| mkdir -p ci-failure-data/test-results | |
| if gh run download "${RUN_ID}" --repo "${REPO}" --name "${ARTIFACT_NAME}" --dir ci-failure-data/test-results 2>&1; then | |
| echo "Download complete." | |
| # List TRX files found | |
| TRX_COUNT=$(find ci-failure-data/test-results -name "*.trx" -type f 2>/dev/null | wc -l) | |
| echo "Found ${TRX_COUNT} TRX file(s):" | |
| find ci-failure-data/test-results -name "*.trx" -type f 2>/dev/null | while IFS= read -r f; do | |
| echo " - $(basename "$f") ($(stat -c%s "$f" 2>/dev/null || echo "?") bytes)" | |
| done || true | |
| # Parse TRX files for failed tests using yq (pre-installed) + jq. | |
| # yq converts XML to JSON, then jq extracts failed test info. | |
| # TRX uses UnitTestResult elements with outcome="Failed" containing | |
| # Output/ErrorInfo/Message and Output/ErrorInfo/StackTrace. | |
| > ci-failure-data/test-failures.jsonl | |
| find ci-failure-data/test-results -name "*.trx" -type f 2>/dev/null | while IFS= read -r TRX_FILE; do | |
| echo "Processing: $(basename "$TRX_FILE")" | |
| yq -p xml -o json '.' "$TRX_FILE" 2>/dev/null | jq -r ' | |
| # Navigate to UnitTestResult — may be array or single object | |
| (.TestRun.Results.UnitTestResult // []) | | |
| (if type == "array" then . else [.] end) | | |
| map(select(.["+@outcome"] == "Failed")) | | |
| .[] | | |
| { | |
| test: (.["+@testName"] // ""), | |
| error: ((.Output.ErrorInfo.Message // "") | if type == "object" then (.["+content"] // "") else tostring end | .[0:1000]), | |
| stack_trace: ((.Output.ErrorInfo.StackTrace // "") | if type == "object" then (.["+content"] // "") else tostring end | .[0:2000]) | |
| } | |
| ' >> ci-failure-data/test-failures.jsonl 2>/dev/null || true | |
| done | |
| jq -s '.' ci-failure-data/test-failures.jsonl > ci-failure-data/test-failures.json 2>/dev/null || echo "[]" > ci-failure-data/test-failures.json | |
| rm -f ci-failure-data/test-failures.jsonl | |
| echo "Extracted $(jq 'length' ci-failure-data/test-failures.json) test failure(s) from TRX files" | |
| # Clean up the extracted files to save space in artifact | |
| rm -rf ci-failure-data/test-results | |
| else | |
| echo "Warning: Failed to download test results artifact" | |
| fi | |
| else | |
| echo "No test results artifact found for run ${RUN_ID}" | |
| fi | |
| echo "Data collection complete." | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| MANUAL_RUN_ID: ${{ inputs.run_id }} | |
| REPO: ${{ github.repository }} | |
| WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} | |
| - name: Create analysis summary | |
| if: steps.collect.outputs.has_work == 'true' | |
| run: | | |
| set -euo pipefail | |
| # Create a structured summary of the failure data for the agent | |
| { | |
| echo "# CI Failure Analysis Data" | |
| echo "" | |
| echo "## Run Information" | |
| echo "- **Run ID**: ${RUN_ID}" | |
| echo "- **Run Attempt**: ${RUN_ATTEMPT}" | |
| echo "- **Run URL**: ${RUN_URL}" | |
| echo "- **Associated PRs**: ${PR_NUMBERS}" | |
| echo "" | |
| echo "## Failed Jobs" | |
| echo "" | |
| jq -r '.[] | "### Job: \(.name)\n- **ID**: \(.id)\n- **Conclusion**: \(.conclusion)\n- **URL**: \(.html_url // "N/A")\n- **Failed Steps**: \([.steps[]? | select(.conclusion == "failure" or .conclusion == "cancelled" or .conclusion == "timed_out") | .name] | join(", "))\n"' \ | |
| ci-failure-data/failed-jobs.json | |
| echo "## Job Logs (Error-Focused)" | |
| echo "" | |
| for LOG_FILE in ci-failure-data/job-*.log; do | |
| if [ -f "${LOG_FILE}" ]; then | |
| JOB_ID=$(basename "${LOG_FILE}" | sed 's/job-\(.*\)\.log/\1/') | |
| JOB_NAME=$(jq -r ".[] | select(.id == ${JOB_ID}) | .name" ci-failure-data/failed-jobs.json 2>/dev/null || echo "Unknown") | |
| echo "### Logs: ${JOB_NAME} (${JOB_ID})" | |
| echo '```' | |
| cat "${LOG_FILE}" | |
| echo '```' | |
| echo "" | |
| fi | |
| done | |
| echo "## Job Annotations" | |
| echo "" | |
| for ANN_FILE in ci-failure-data/annotations-*.json; do | |
| if [ -f "${ANN_FILE}" ]; then | |
| JOB_ID=$(basename "${ANN_FILE}" | sed 's/annotations-\(.*\)\.json/\1/') | |
| JOB_NAME=$(jq -r ".[] | select(.id == ${JOB_ID}) | .name" ci-failure-data/failed-jobs.json 2>/dev/null || echo "Unknown") | |
| ANN_COUNT=$(jq 'length' "${ANN_FILE}" 2>/dev/null || echo "0") | |
| if [ "${ANN_COUNT}" -gt 0 ]; then | |
| echo "### Annotations: ${JOB_NAME} (${JOB_ID})" | |
| jq -r '.[] | "- **\(.annotation_level // "unknown")**: \(.message // "no message")"' "${ANN_FILE}" 2>/dev/null || true | |
| echo "" | |
| fi | |
| fi | |
| done | |
| echo "## Test Failures (from TRX artifacts)" | |
| echo "" | |
| if [ -f "ci-failure-data/test-failures.json" ]; then | |
| FAILURE_COUNT=$(jq 'length' ci-failure-data/test-failures.json 2>/dev/null || echo "0") | |
| if [ "${FAILURE_COUNT}" -gt 0 ]; then | |
| jq -r '.[] | "### `\(.test)`\n\n**Error:**\n```\n\(.error)\n```\n" + (if .stack_trace != "" then "**Stack Trace:**\n```\n\(.stack_trace)\n```\n" else "" end)' ci-failure-data/test-failures.json 2>/dev/null || echo "No parseable test failures." | |
| else | |
| echo "No test failures extracted from TRX artifacts." | |
| fi | |
| else | |
| echo "No test results artifact available." | |
| fi | |
| echo "" | |
| echo "## Pull Request" | |
| echo "" | |
| if [ -f "ci-failure-data/pr-metadata.json" ]; then | |
| jq -r '"- **PR**: #\(.number) \(.title)\n- **Author**: @\(.user)\n- **State**: \(.state)\n- **Branch**: \(.head_branch) → \(.base_branch)\n- **URL**: \(.html_url)"' ci-failure-data/pr-metadata.json 2>/dev/null || echo "No PR metadata available." | |
| else | |
| echo "No PR metadata available." | |
| fi | |
| echo "" | |
| echo "## PR Changed Files" | |
| echo "" | |
| if [ -f "ci-failure-data/pr-files.json" ]; then | |
| jq -r '.[] | "- \(.filename) (\(.status), +\(.additions)/-\(.deletions))"' ci-failure-data/pr-files.json 2>/dev/null || echo "No file data available." | |
| else | |
| echo "No PR file data available." | |
| fi | |
| echo "" | |
| echo "## Known Transient Failure Patterns" | |
| echo "" | |
| if [ -f "ci-failure-data/retry-patterns.json" ]; then | |
| echo "### Test Failure Patterns" | |
| jq -r '.testFailurePatterns[]? | "- \(.reason // "unnamed"): \(if .output | type == "string" then .output else .output.regex end)"' \ | |
| ci-failure-data/retry-patterns.json 2>/dev/null || echo "None loaded." | |
| echo "" | |
| echo "### Job Failure Patterns" | |
| jq -r '.jobFailurePatterns[]? | "- \(.reason // "unnamed"): \(if .output | type == "string" then .output else .output.regex end)"' \ | |
| ci-failure-data/retry-patterns.json 2>/dev/null || echo "None loaded." | |
| else | |
| echo "No retry patterns file found." | |
| fi | |
| echo "" | |
| echo "## Prior Causes (from memory branch)" | |
| echo "" | |
| echo "These are previously identified CI failure causes. If this run's" | |
| echo "failure matches an existing cause, reuse the same cause ID and" | |
| echo "append a new occurrence rather than creating a duplicate." | |
| echo "" | |
| if [ -d "ci-failure-data/prior-causes" ] && [ "$(find ci-failure-data/prior-causes -name '*.json' -type f 2>/dev/null | wc -l)" -gt 0 ]; then | |
| for CAUSE_FILE in ci-failure-data/prior-causes/*.json; do | |
| [ -f "$CAUSE_FILE" ] || continue | |
| jq -r '"### `\(.id)`\n- **Type**: \(.type)\n- **Title**: \(.title)\n- **Test**: \(.test_name // "N/A")\n- **Issue**: \(.issue_url // "none")\n- **Error pattern**: \(.error_pattern | .[0:300])\n- **Occurrences**: \(.occurrences | length)\n- **Last seen**: \(.occurrences | sort_by(.observed_at) | last | .observed_at // "unknown")\n"' \ | |
| "$CAUSE_FILE" 2>/dev/null || true | |
| done | |
| else | |
| echo "No prior causes available (first run or memory branch not initialized)." | |
| fi | |
| } > ci-failure-data/analysis-summary.md | |
| echo "Analysis summary written to ci-failure-data/analysis-summary.md" | |
| env: | |
| PR_NUMBERS: ${{ steps.collect.outputs.pr_numbers }} | |
| RUN_ATTEMPT: ${{ steps.collect.outputs.run_attempt }} | |
| RUN_ID: ${{ steps.collect.outputs.run_id }} | |
| RUN_URL: ${{ steps.collect.outputs.run_url }} | |
| - if: steps.collect.outputs.has_work == 'true' | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: ci-failure-data | |
| path: ci-failure-data/ | |
| conclusion: | |
| needs: | |
| - activation | |
| - agent | |
| - collect-data | |
| - detection | |
| - publish_data | |
| - rerun_failed_jobs | |
| - safe_outputs | |
| if: > | |
| always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || | |
| needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') | |
| runs-on: ubuntu-slim | |
| permissions: {} | |
| concurrency: | |
| group: "gh-aw-conclusion-analyze-ci-failure" | |
| cancel-in-progress: false | |
| queue: max | |
| outputs: | |
| incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} | |
| noop_message: ${{ steps.noop.outputs.noop_message }} | |
| tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} | |
| total_count: ${{ steps.missing_tool.outputs.total_count }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Collect usage artifact files | |
| if: always() | |
| continue-on-error: true | |
| run: | | |
| mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection | |
| echo "Usage artifact source file status:" | |
| for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do | |
| [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" | |
| done | |
| [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true | |
| [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true | |
| [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true | |
| [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true | |
| [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl | |
| [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl | |
| find /tmp/gh-aw/usage -type f -print | sort | |
| - name: Upload usage artifact | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: usage | |
| path: | | |
| /tmp/gh-aw/usage/aw-info.jsonl | |
| /tmp/gh-aw/usage/agent_usage.jsonl | |
| /tmp/gh-aw/usage/detection_usage.jsonl | |
| /tmp/gh-aw/usage/agent/token_usage.jsonl | |
| /tmp/gh-aw/usage/detection/token_usage.jsonl | |
| if-no-files-found: ignore | |
| - name: Process no-op messages | |
| id: noop | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_NOOP_MAX: "1" | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} | |
| GH_AW_NOOP_REPORT_AS_ISSUE: "true" | |
| GH_AW_AIC: ${{ needs.agent.outputs.aic }} | |
| GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} | |
| GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} | |
| GH_AW_WORKFLOW_ID: "analyze-ci-failure" | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); | |
| await main(); | |
| - name: Log detection run | |
| id: detection_runs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} | |
| GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); | |
| await main(); | |
| - name: Record missing tool | |
| id: missing_tool | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); | |
| await main(); | |
| - name: Record incomplete | |
| id: report_incomplete | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); | |
| await main(); | |
| - name: Handle agent failure | |
| id: handle_agent_failure | |
| if: always() | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} | |
| GH_AW_WORKFLOW_ID: "analyze-ci-failure" | |
| GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" | |
| GH_AW_ENGINE_ID: "copilot" | |
| GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} | |
| GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} | |
| GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} | |
| GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} | |
| GH_AW_AIC: ${{ needs.agent.outputs.aic }} | |
| GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} | |
| GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} | |
| GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} | |
| GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} | |
| GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} | |
| GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} | |
| GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" | |
| GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} | |
| GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} | |
| GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} | |
| GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} | |
| GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} | |
| GH_AW_GROUP_REPORTS: "false" | |
| GH_AW_FAILURE_REPORT_AS_ISSUE: "true" | |
| GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" | |
| GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" | |
| GH_AW_TIMEOUT_MINUTES: "20" | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); | |
| await main(); | |
| detection: | |
| needs: | |
| - activation | |
| - agent | |
| if: > | |
| always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| copilot-requests: write | |
| outputs: | |
| aic: ${{ steps.parse_detection_token_usage.outputs.aic }} | |
| detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} | |
| detection_reason: ${{ steps.detection_conclusion.outputs.reason }} | |
| detection_success: ${{ steps.detection_conclusion.outputs.success }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Checkout repository for patch context | |
| if: needs.agent.outputs.has_patch == 'true' | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| # --- Threat Detection --- | |
| - name: Clean stale firewall files from agent artifact | |
| run: | | |
| rm -rf /tmp/gh-aw/sandbox/firewall/logs | |
| rm -rf /tmp/gh-aw/sandbox/firewall/audit | |
| - name: Download container images | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 | |
| - name: Check if detection needed | |
| id: detection_guard | |
| if: always() | |
| env: | |
| OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} | |
| HAS_PATCH: ${{ needs.agent.outputs.has_patch }} | |
| run: | | |
| if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then | |
| echo "run_detection=true" >> "$GITHUB_OUTPUT" | |
| echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" | |
| else | |
| echo "run_detection=false" >> "$GITHUB_OUTPUT" | |
| echo "Detection skipped: no agent outputs or patches to analyze" | |
| fi | |
| - name: Clear MCP Config for detection | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" | |
| rm -f "$HOME/.copilot/mcp-config.json" | |
| rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" | |
| - name: Prepare threat detection files | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| mkdir -p /tmp/gh-aw/threat-detection/aw-prompts | |
| rm -f /tmp/gh-aw/agent_usage.json | |
| cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true | |
| if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then | |
| echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." | |
| fi | |
| cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true | |
| for f in /tmp/gh-aw/aw-*.patch; do | |
| [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| done | |
| for f in /tmp/gh-aw/aw-*.bundle; do | |
| [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| done | |
| echo "Prepared threat detection files:" | |
| ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true | |
| - name: Setup threat detection | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| WORKFLOW_NAME: "Analyze CI Failure" | |
| WORKFLOW_DESCRIPTION: "Analyzes failed PR CI builds using Copilot to determine whether the failure\nis transient (flaky test, infrastructure issue) or caused by the PR changes\n(compilation error, test regression). For transient infrastructure failures,\nreruns the CI build. For transient test failures, posts a comment with\ndetails and suggested next steps. For non-transient failures, posts a\ncomment explaining the root cause." | |
| HAS_PATCH: ${{ needs.agent.outputs.has_patch }} | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); | |
| await main(); | |
| - name: Ensure threat-detection directory and log | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| run: | | |
| mkdir -p /tmp/gh-aw/threat-detection | |
| touch /tmp/gh-aw/threat-detection/detection.log | |
| - name: Setup Node.js | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 | |
| with: | |
| node-version: '24' | |
| package-manager-cache: false | |
| - name: Install GitHub Copilot CLI | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 | |
| env: | |
| GH_HOST: github.com | |
| - name: Install AWF binary | |
| run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 | |
| - name: Execute GitHub Copilot CLI | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| continue-on-error: true | |
| id: detection_agentic_execution | |
| # Copilot CLI tool arguments (sorted): | |
| timeout-minutes: 20 | |
| run: | | |
| set -o pipefail | |
| printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt | |
| trap 'rm -f "$HOME/.copilot/settings.json"' EXIT | |
| mkdir -p "$HOME/.copilot" | |
| printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" | |
| export XDG_CONFIG_HOME="$HOME" | |
| touch /tmp/gh-aw/agent-step-summary.md | |
| GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) | |
| export GH_AW_NODE_BIN | |
| export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" | |
| (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) | |
| GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" | |
| printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" | |
| cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json | |
| export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" | |
| GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" | |
| if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then | |
| GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" | |
| fi | |
| GH_AW_TOOL_CACHE_MOUNT="" | |
| GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" | |
| if [ -d "$GH_AW_TOOL_CACHE" ]; then | |
| if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then | |
| GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" | |
| fi | |
| elif [ -d "/home/runner/work/_tool" ]; then | |
| GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" | |
| fi | |
| # shellcheck disable=SC1003 | |
| sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ | |
| -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log | |
| env: | |
| AWF_REFLECT_ENABLED: 1 | |
| COPILOT_AGENT_RUNNER_TYPE: STANDALONE | |
| COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode | |
| COPILOT_GITHUB_TOKEN: ${{ github.token }} | |
| COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'claude-sonnet-4.6' }} | |
| GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} | |
| GH_AW_PHASE: detection | |
| GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt | |
| GH_AW_TIMEOUT_MINUTES: 20 | |
| GH_AW_VERSION: v0.79.8 | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GITHUB_AW: true | |
| GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows | |
| GITHUB_HEAD_REF: ${{ github.head_ref }} | |
| GITHUB_REF_NAME: ${{ github.ref_name }} | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md | |
| GITHUB_WORKSPACE: ${{ github.workspace }} | |
| GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_AUTHOR_NAME: github-actions[bot] | |
| GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com | |
| GIT_COMMITTER_NAME: github-actions[bot] | |
| RUNNER_TEMP: ${{ runner.temp }} | |
| S2STOKENS: true | |
| - name: Parse threat detection token usage for step summary | |
| id: parse_detection_token_usage | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage | |
| with: | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); | |
| await main(); | |
| - name: Upload threat detection log | |
| if: always() && steps.detection_guard.outputs.run_detection == 'true' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: detection | |
| path: /tmp/gh-aw/threat-detection/detection.log | |
| if-no-files-found: ignore | |
| - name: Parse and conclude threat detection | |
| id: detection_conclusion | |
| if: always() | |
| continue-on-error: true | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} | |
| DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} | |
| GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" | |
| with: | |
| script: | | |
| try { | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); | |
| await main(); | |
| } catch (loadErr) { | |
| const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; | |
| const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; | |
| const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); | |
| core.error(msg); | |
| core.setOutput('reason', 'parse_error'); | |
| if (continueOnError && !detectionExecutionFailed) { | |
| core.warning('\u26A0\uFE0F ' + msg); | |
| core.setOutput('conclusion', 'warning'); | |
| core.setOutput('success', 'false'); | |
| } else { | |
| core.setOutput('conclusion', 'failure'); | |
| core.setOutput('success', 'false'); | |
| core.setFailed(msg); | |
| } | |
| } | |
| pre_activation: | |
| runs-on: ubuntu-slim | |
| outputs: | |
| activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} | |
| matched_command: '' | |
| setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} | |
| setup-span-id: ${{ steps.setup.outputs.span-id }} | |
| setup-trace-id: ${{ steps.setup.outputs.trace-id }} | |
| steps: | |
| - name: Setup Scripts | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Check team membership for workflow | |
| id: check_membership | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_REQUIRED_ROLES: "admin,maintainer,write" | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); | |
| await main(); | |
| publish_data: | |
| name: Publish analysis data and comment on PR | |
| needs: | |
| - agent | |
| - detection | |
| - safe_outputs | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'publish_data') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Download agent output artifact | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: ${{ runner.temp }}/gh-aw/safe-jobs/ | |
| - name: Publish analysis data and comment on PR | |
| run: | | |
| set -euo pipefail | |
| OUTPUT_FILE="$GH_AW_AGENT_OUTPUT" | |
| if [ -z "$OUTPUT_FILE" ]; then | |
| echo "::error::No GH_AW_AGENT_OUTPUT environment variable found" | |
| exit 1 | |
| fi | |
| ARTIFACT_DIR=$(dirname "$OUTPUT_FILE") | |
| ANALYSIS_FILE="$ARTIFACT_DIR/agent/analysis-result.json" | |
| CAUSES_DIR="$ARTIFACT_DIR/agent/causes" | |
| if [ ! -f "$ANALYSIS_FILE" ]; then | |
| echo "::error::Analysis result not found at $ANALYSIS_FILE" | |
| exit 1 | |
| fi | |
| # Validate summary JSON | |
| if ! jq empty "$ANALYSIS_FILE" 2>/dev/null; then | |
| echo "::error::analysis-result.json is not valid JSON" | |
| exit 1 | |
| fi | |
| # Validate cause files | |
| if [ -d "$CAUSES_DIR" ]; then | |
| for CAUSE_FILE in "$CAUSES_DIR"/*.json; do | |
| [ -f "$CAUSE_FILE" ] || continue | |
| if ! jq empty "$CAUSE_FILE" 2>/dev/null; then | |
| echo "::warning::Invalid JSON in cause file: $(basename "$CAUSE_FILE")" | |
| fi | |
| done | |
| fi | |
| REPO="${{ github.repository }}" | |
| MEMORY_BRANCH="memory/ci-failure-analysis" | |
| # Read fields from the analysis JSON | |
| RUN_ID=$(jq -r '.run_id' "$ANALYSIS_FILE") | |
| VERDICT=$(jq -r '.verdict' "$ANALYSIS_FILE") | |
| RUN_URL=$(jq -r '.run_url // ""' "$ANALYSIS_FILE") | |
| # Build a comma-separated list of PR numbers. The JSON schema has | |
| # a single pr.number; if the collect-data job passed multiple PRs | |
| # in the future, extend the agent schema accordingly. | |
| PR_NUMBERS=$(jq -r '.pr.number // "" | tostring' "$ANALYSIS_FILE") | |
| # ── 1. Set up memory branch and merge cause data ── | |
| # Skip persisting data for code-issue verdicts — these are not | |
| # actionable by CI automation and would just add noise. | |
| if [ "$VERDICT" = "code-issue" ]; then | |
| echo "Verdict is code-issue. Skipping memory branch persistence." | |
| else | |
| if ! git clone --depth 1 --branch "$MEMORY_BRANCH" \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" \ | |
| memory-repo 2>/dev/null; then | |
| echo "Memory branch does not exist yet, creating orphan branch" | |
| git init memory-repo | |
| git -C memory-repo checkout --orphan "$MEMORY_BRANCH" | |
| git -C memory-repo remote add origin \ | |
| "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" | |
| fi | |
| git -C memory-repo config user.name "github-actions[bot]" | |
| git -C memory-repo config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Store run summary under runs/ directory | |
| mkdir -p "memory-repo/runs" | |
| cp "$ANALYSIS_FILE" "memory-repo/runs/${RUN_ID}.json" | |
| # Store individual cause files under causes/ (shared across runs). | |
| # Each cause file accumulates occurrences over time. The agent | |
| # writes cause definitions (no occurrences); we build the occurrence | |
| # from the run summary and merge it into the stored cause file. | |
| if [ -d "$CAUSES_DIR" ]; then | |
| mkdir -p "memory-repo/causes" | |
| # Build the occurrence entry from the run summary JSON | |
| ANALYZED_AT=$(jq -r '.analyzed_at' "$ANALYSIS_FILE") | |
| PR_NUMBER=$(jq -r '.pr.number // 0' "$ANALYSIS_FILE") | |
| # Find the first failed job name for context in the occurrence | |
| FIRST_JOB=$(jq -r '.failed_jobs[0].name // "unknown"' "$ANALYSIS_FILE") | |
| for CAUSE_FILE in "$CAUSES_DIR"/*.json; do | |
| [ -f "$CAUSE_FILE" ] || continue | |
| # Skip code-issue causes — only persist transient/flaky causes. | |
| CAUSE_TYPE_CHECK=$(jq -r '.type' "$CAUSE_FILE" 2>/dev/null || echo "") | |
| if [ "$CAUSE_TYPE_CHECK" = "code-issue" ]; then | |
| continue | |
| fi | |
| CAUSE_BASENAME=$(basename "$CAUSE_FILE") | |
| EXISTING="memory-repo/causes/${CAUSE_BASENAME}" | |
| # Add an occurrences array with this run's entry to the agent's cause file | |
| CAUSE_WITH_OCC=$(jq --argjson run_id "$RUN_ID" \ | |
| --arg run_url "$RUN_URL" \ | |
| --arg job "$FIRST_JOB" \ | |
| --argjson pr_number "$PR_NUMBER" \ | |
| --arg observed_at "$ANALYZED_AT" \ | |
| '. + {occurrences: [{run_id: $run_id, run_url: $run_url, job: $job, pr_number: $pr_number, observed_at: $observed_at}]}' \ | |
| "$CAUSE_FILE") | |
| if [ -f "$EXISTING" ]; then | |
| # Merge: append new occurrence, deduplicate by run_id | |
| echo "$CAUSE_WITH_OCC" | jq -s --slurpfile existing "$EXISTING" ' | |
| .[0] as $new | $existing[0] as $ex | | |
| ($new | del(.occurrences)) * { | |
| occurrences: ( | |
| [$ex.occurrences[], $new.occurrences[]] | |
| | unique_by(.run_id) | |
| | sort_by(.observed_at) | |
| ) | |
| } | |
| ' > "${EXISTING}.tmp" && mv "${EXISTING}.tmp" "$EXISTING" | |
| else | |
| echo "$CAUSE_WITH_OCC" > "$EXISTING" | |
| fi | |
| done | |
| CAUSE_COUNT=$(find "memory-repo/causes" -name '*.json' -type f 2>/dev/null | wc -l) | |
| echo "Persisted cause files to causes/ (${CAUSE_COUNT} total)" | |
| fi | |
| # ── 2. Create or update issues for each cause ── | |
| if [ -d "$CAUSES_DIR" ]; then | |
| # Build occurrence info from the run summary for issue updates | |
| ANALYZED_AT=$(jq -r '.analyzed_at' "$ANALYSIS_FILE") | |
| PR_NUMBER=$(jq -r '.pr.number // 0' "$ANALYSIS_FILE") | |
| FIRST_JOB=$(jq -r '.failed_jobs[0].name // "unknown"' "$ANALYSIS_FILE") | |
| # Build the occurrence table row for this run | |
| OCC_DATE=$(echo "$ANALYZED_AT" | cut -dT -f1) | |
| NEW_OCCURRENCE_ROW="| ${OCC_DATE} | [${RUN_ID}](${RUN_URL}) | ${FIRST_JOB} | #${PR_NUMBER} |" | |
| for CAUSE_FILE in "$CAUSES_DIR"/*.json; do | |
| [ -f "$CAUSE_FILE" ] || continue | |
| jq empty "$CAUSE_FILE" 2>/dev/null || continue | |
| CAUSE_ID=$(jq -r '.id' "$CAUSE_FILE") | |
| # Validate CAUSE_ID is a safe slug (lowercase alphanumeric + hyphens) | |
| # to prevent HTML comment injection via the marker. | |
| if ! echo "$CAUSE_ID" | grep -qP '^[a-z0-9][a-z0-9-]*$'; then | |
| echo "::warning::Invalid cause ID '${CAUSE_ID}', skipping" | |
| continue | |
| fi | |
| CAUSE_TYPE=$(jq -r '.type' "$CAUSE_FILE") | |
| # Skip issue creation for code-issue causes — those are the | |
| # PR author's responsibility, not a recurring CI problem. | |
| if [ "$CAUSE_TYPE" = "code-issue" ]; then | |
| echo "Skipping issue for code-issue cause: ${CAUSE_ID}" | |
| continue | |
| fi | |
| CAUSE_STORED="memory-repo/causes/${CAUSE_ID}.json" | |
| MARKER="<!-- ci-failure-cause:${CAUSE_ID} -->" | |
| # Check if the stored cause file already has a linked issue | |
| EXISTING_ISSUE="" | |
| if [ -f "$CAUSE_STORED" ]; then | |
| STORED_ISSUE_URL=$(jq -r '.issue_url // empty' "$CAUSE_STORED") | |
| if [ -n "$STORED_ISSUE_URL" ]; then | |
| # Extract issue number from URL (e.g. .../issues/1234 -> 1234) | |
| EXISTING_ISSUE=$(echo "$STORED_ISSUE_URL" | grep -oP '\d+$' || true) | |
| # Verify issue still exists | |
| if [ -n "$EXISTING_ISSUE" ]; then | |
| ISSUE_STATE=$(gh api "repos/${REPO}/issues/${EXISTING_ISSUE}" --jq '.state' 2>/dev/null || echo "") | |
| if [ -z "$ISSUE_STATE" ]; then | |
| echo "Linked issue #${EXISTING_ISSUE} no longer exists, will create new" | |
| EXISTING_ISSUE="" | |
| fi | |
| fi | |
| fi | |
| fi | |
| # If no stored issue link, fall back to searching by marker | |
| REOPEN="false" | |
| if [ -z "$EXISTING_ISSUE" ]; then | |
| # Fetch labeled issues for marker search (lazy-loaded once) | |
| if [ -z "${ISSUES_CACHE_LOADED:-}" ]; then | |
| OPEN_ISSUES_CACHE=$(mktemp) | |
| CLOSED_ISSUES_CACHE=$(mktemp) | |
| gh issue list --repo "$REPO" --label "ci-failure-cause" --state open --limit 500 --json number,body \ | |
| > "$OPEN_ISSUES_CACHE" 2>/dev/null || echo '[]' > "$OPEN_ISSUES_CACHE" | |
| gh issue list --repo "$REPO" --label "ci-failure-cause" --state closed --limit 500 --json number,body \ | |
| > "$CLOSED_ISSUES_CACHE" 2>/dev/null || echo '[]' > "$CLOSED_ISSUES_CACHE" | |
| ISSUES_CACHE_LOADED="true" | |
| fi | |
| EXISTING_ISSUE=$(jq -r --arg marker "$MARKER" '.[] | select(.body | contains($marker)) | .number' "$OPEN_ISSUES_CACHE" | head -1 || true) | |
| if [ -z "$EXISTING_ISSUE" ]; then | |
| EXISTING_ISSUE=$(jq -r --arg marker "$MARKER" '.[] | select(.body | contains($marker)) | .number' "$CLOSED_ISSUES_CACHE" | head -1 || true) | |
| if [ -n "$EXISTING_ISSUE" ]; then | |
| REOPEN="true" | |
| fi | |
| fi | |
| else | |
| # Check if the stored issue is closed (may need reopening) | |
| if [ "$ISSUE_STATE" = "closed" ]; then | |
| REOPEN="true" | |
| fi | |
| fi | |
| if [ -n "$EXISTING_ISSUE" ]; then | |
| # Store issue URL in the cause file on memory branch | |
| ISSUE_URL="https://github.com/${REPO}/issues/${EXISTING_ISSUE}" | |
| if [ -f "$CAUSE_STORED" ]; then | |
| jq --arg url "$ISSUE_URL" '.issue_url = $url' "$CAUSE_STORED" > "${CAUSE_STORED}.tmp" \ | |
| && mv "${CAUSE_STORED}.tmp" "$CAUSE_STORED" | |
| fi | |
| # Append new occurrence rows to the existing issue body, skipping | |
| # if this run_id is already recorded (avoids duplicates on re-runs). | |
| CURRENT_BODY=$(gh api "repos/${REPO}/issues/${EXISTING_ISSUE}" --jq '.body // ""') | |
| # Anchor the pattern with '(' from the markdown link to avoid | |
| # partial matches (e.g., run 123 matching run 1234). | |
| if echo "$CURRENT_BODY" | grep -qF "[${RUN_ID}]("; then | |
| echo "Occurrence for run ${RUN_ID} already recorded in issue #${EXISTING_ISSUE}. Skipping." | |
| else | |
| BODY_FILE=$(mktemp) | |
| printf '%s\n%s\n' "$CURRENT_BODY" "$NEW_OCCURRENCE_ROW" > "$BODY_FILE" | |
| gh issue edit "$EXISTING_ISSUE" --repo "$REPO" --body-file "$BODY_FILE" | |
| rm -f "$BODY_FILE" | |
| fi | |
| if [ "$REOPEN" = "true" ]; then | |
| gh issue reopen "$EXISTING_ISSUE" --repo "$REPO" | |
| echo "Reopened and updated issue #${EXISTING_ISSUE} for cause: ${CAUSE_ID}" | |
| else | |
| echo "Updated issue #${EXISTING_ISSUE} for cause: ${CAUSE_ID}" | |
| fi | |
| else | |
| # Create a new issue for this cause | |
| BODY_FILE=$(mktemp) | |
| TEST_NAME=$(jq -r '.test_name // empty' "$CAUSE_FILE") | |
| { | |
| echo "${MARKER}" | |
| echo "" | |
| echo "## Build Information" | |
| echo "" | |
| echo "Build: ${RUN_URL}" | |
| if [ -n "$TEST_NAME" ]; then | |
| echo "Build error leg or test failing: ${FIRST_JOB} / \`${TEST_NAME}\`" | |
| else | |
| echo "Build error leg: ${FIRST_JOB}" | |
| fi | |
| echo "Pull request: #${PR_NUMBER}" | |
| echo "" | |
| echo "## Error Message" | |
| echo "" | |
| echo '```' | |
| jq -r '.error_pattern' "$CAUSE_FILE" | |
| echo '```' | |
| echo "" | |
| echo "## Description" | |
| echo "" | |
| jq -r '.title' "$CAUSE_FILE" | |
| echo "" | |
| echo "**Type**: ${CAUSE_TYPE}" | |
| echo "" | |
| echo "## Occurrences" | |
| echo "" | |
| echo "| Date | Build | Job | PR |" | |
| echo "|------|-------|-----|----|" | |
| echo "$NEW_OCCURRENCE_ROW" | |
| } > "$BODY_FILE" | |
| LABELS="ci-failure-cause" | |
| if [ "$CAUSE_TYPE" = "flaky-test" ]; then | |
| LABELS="ci-failure-cause,test-failure" | |
| fi | |
| # Build the title via jq to avoid shell metacharacter issues | |
| # with agent-generated cause titles. | |
| ISSUE_TITLE=$(jq -r '"[CI Failure] " + .title' "$CAUSE_FILE") | |
| CREATED_ISSUE_URL=$(gh issue create --repo "$REPO" \ | |
| --title "$ISSUE_TITLE" \ | |
| --label "$LABELS" \ | |
| --body-file "$BODY_FILE") | |
| rm -f "$BODY_FILE" | |
| echo "Created issue for cause: ${CAUSE_ID} — ${CREATED_ISSUE_URL}" | |
| # Store issue URL in the cause file on memory branch | |
| if [ -f "$CAUSE_STORED" ]; then | |
| jq --arg url "$CREATED_ISSUE_URL" '.issue_url = $url' "$CAUSE_STORED" > "${CAUSE_STORED}.tmp" \ | |
| && mv "${CAUSE_STORED}.tmp" "$CAUSE_STORED" | |
| fi | |
| fi | |
| done | |
| rm -f "${OPEN_ISSUES_CACHE:-}" "${CLOSED_ISSUES_CACHE:-}" | |
| fi | |
| # ── 3. Push memory branch ── | |
| git -C memory-repo add -A | |
| if git -C memory-repo diff --cached --quiet; then | |
| echo "No changes to memory branch" | |
| else | |
| git -C memory-repo commit -m "Add CI failure analysis for run ${RUN_ID}" | |
| git -C memory-repo push origin "HEAD:$MEMORY_BRANCH" | |
| echo "Memory branch updated with analysis for run ${RUN_ID}" | |
| fi | |
| fi | |
| # ── 4. Post PR comment using the analysis JSON ── | |
| FIRST_PR=$(echo "$PR_NUMBERS" | cut -d',' -f1) | |
| if [ -z "$FIRST_PR" ] || [ "$FIRST_PR" = "null" ]; then | |
| echo "No PR number found in analysis. Skipping comment." | |
| exit 0 | |
| fi | |
| # Check PR is not locked (still comment on closed PRs) | |
| PR_LOCKED=$(gh api "repos/${REPO}/pulls/${FIRST_PR}" --jq '.locked' 2>/dev/null || echo "false") | |
| if [ "$PR_LOCKED" = "true" ]; then | |
| echo "PR #${FIRST_PR} is locked. Skipping comment." | |
| exit 0 | |
| fi | |
| # Build comment body from the analysis JSON and write to a file | |
| # to avoid shell expansion issues and ARG_MAX limits. | |
| COMMENT_FILE=$(mktemp) | |
| jq -r ' | |
| def job_list: | |
| [.failed_jobs[] | "- `\(.name)` — \(.reason) (\(.classification))"] | |
| | join("\n"); | |
| def test_list: | |
| [.failed_tests[]? | select(.classification == "flaky") | | |
| "- `\(.name)` in job `\(.job)`\n - **Error**: \(.error)\n" + | |
| (if (.stack_trace // "") != "" then " - **Stack Trace** (first frames):\n ```\n \(.stack_trace | split("\n") | .[0:5] | join("\n "))\n ```\n" else "" end) + | |
| " - **Why likely flaky**: \(.reason)"] | |
| | join("\n"); | |
| "<!-- analyze-ci-failure -->\n" + | |
| if .verdict == "transient-infra" then | |
| "🔍 **CI Failure Analysis: Transient Infrastructure Failure**\n\nThe CI build failed due to transient infrastructure issues.\n\n**Failed jobs:**\n" + job_list + "\n\nIf a rerun was not already requested automatically, visit the [workflow run page](" + .run_url + ") to rerun the failed jobs manually.\n" | |
| elif .verdict == "flaky-test" then | |
| "⚠️ **CI Failure Analysis: Possible Flaky Test(s)**\n\nThe CI build failed due to test failure(s) that appear unrelated to the PR changes. These may be flaky tests.\n\n**Suspected flaky test(s):**\n" + test_list + "\n\n**Suggested actions:**\n- Re-run the failed CI jobs to confirm if the failure is intermittent\n- If the test continues to fail, consider [quarantining it](https://github.com/microsoft/aspire/blob/main/docs/quarantined-tests.md) using `/quarantine-test <test name> <issue URL>`\n- Search [existing issues](https://github.com/microsoft/aspire/issues?q=is%3Aissue+label%3Atest-failure) to see if this test is already known to be flaky\n\nYou can re-run the failed jobs from the [workflow run page](" + .run_url + ").\n" | |
| elif .verdict == "code-issue" then | |
| "❌ **CI Failure Analysis: Code Issue Detected**\n\nThe CI build failed due to issue(s) caused by changes in this PR.\n\n**Failed jobs:**\n" + job_list + "\n\nThe CI will not be automatically rerun. Please fix the issue and push an updated commit.\n" | |
| else | |
| "⚠️ **CI Failure Analysis: Mixed Failures**\n\nThe CI build contains both transient and non-transient failures.\n\n**Failed jobs:**\n" + job_list + "\n\nThe CI will not be automatically rerun. Please review the failures above.\n" | |
| end | |
| ' "$ANALYSIS_FILE" > "$COMMENT_FILE" | |
| # Update an existing analysis comment if one exists (by marker), | |
| # otherwise create a new one. This prevents stacking duplicate | |
| # comments on PRs with repeated CI failures. | |
| MARKER="<!-- analyze-ci-failure -->" | |
| EXISTING_COMMENT_ID=$(gh api "repos/${REPO}/issues/${FIRST_PR}/comments" --paginate \ | |
| --jq ".[] | select(.body | contains(\"${MARKER}\")) | .id" 2>/dev/null | head -1 || true) | |
| if [ -n "$EXISTING_COMMENT_ID" ]; then | |
| gh api --method PATCH "repos/${REPO}/issues/comments/${EXISTING_COMMENT_ID}" \ | |
| -f body="$(cat "$COMMENT_FILE")" > /dev/null | |
| echo "Updated existing analysis comment (ID: ${EXISTING_COMMENT_ID}) on PR #${FIRST_PR}" | |
| else | |
| gh pr comment "$FIRST_PR" --repo "$REPO" --body-file "$COMMENT_FILE" | |
| echo "Posted new analysis comment on PR #${FIRST_PR}" | |
| fi | |
| rm -f "$COMMENT_FILE" | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json | |
| GH_TOKEN: ${{ github.token }} | |
| rerun_failed_jobs: | |
| name: Rerun failed CI jobs | |
| needs: | |
| - agent | |
| - detection | |
| - safe_outputs | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'rerun_failed_jobs') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| actions: write | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Download agent output artifact | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: ${{ runner.temp }}/gh-aw/safe-jobs/ | |
| - name: Rerun failed jobs | |
| uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 | |
| env: | |
| ENABLE_RERUN: ${{ env.ENABLE_RERUN }} | |
| GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| // Read inputs from the agent output artifact. | |
| // gh-aw writes { "items": [ { "type": "rerun_failed_jobs", ... } ] }. | |
| const outputFile = process.env.GH_AW_AGENT_OUTPUT; | |
| if (!outputFile || !fs.existsSync(outputFile)) { | |
| core.setFailed('Agent output file not found'); | |
| return; | |
| } | |
| const payload = JSON.parse(fs.readFileSync(outputFile, 'utf8')); | |
| const items = (payload && Array.isArray(payload.items)) ? payload.items : []; | |
| const item = items.find(i => i && i.type === 'rerun_failed_jobs'); | |
| if (!item) { | |
| core.info('No rerun_failed_jobs items in agent output.'); | |
| return; | |
| } | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const runId = Number(item.run_id); | |
| const prNumbers = String(item.pr_numbers).split(',').map(Number).filter(n => n > 0); | |
| const reason = item.reason || ''; | |
| const enableRerun = String(process.env.ENABLE_RERUN).toLowerCase() === 'true'; | |
| if (!Number.isInteger(runId) || runId <= 0) { | |
| core.setFailed(`Invalid run_id: ${item.run_id}`); | |
| return; | |
| } | |
| if (!enableRerun) { | |
| core.info(`Dry-run mode (ENABLE_RERUN is not 'true'). Would have rerun failed jobs for run ${runId}. Reason: ${reason}`); | |
| return; | |
| } | |
| // Verify at least one PR is still open | |
| let hasOpenPr = false; | |
| for (const prNumber of prNumbers) { | |
| try { | |
| const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber }); | |
| if (pr.state === 'open') { | |
| hasOpenPr = true; | |
| break; | |
| } | |
| } catch (e) { | |
| core.warning(`Failed to check PR #${prNumber}: ${e.message}`); | |
| } | |
| } | |
| if (!hasOpenPr) { | |
| core.info('All associated PRs are closed. Skipping rerun.'); | |
| return; | |
| } | |
| // Request rerun of failed jobs | |
| await github.rest.actions.reRunWorkflowFailedJobs({ | |
| owner, | |
| repo, | |
| run_id: runId, | |
| }); | |
| core.info(`Requested rerun of failed jobs for run ${runId}. Reason: ${reason}`); | |
| safe_outputs: | |
| needs: | |
| - activation | |
| - agent | |
| - detection | |
| if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' | |
| runs-on: ubuntu-slim | |
| permissions: {} | |
| timeout-minutes: 45 | |
| env: | |
| GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} | |
| GH_AW_AIC: ${{ needs.agent.outputs.aic }} | |
| GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} | |
| GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/analyze-ci-failure" | |
| GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} | |
| GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} | |
| GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} | |
| GH_AW_ENGINE_ID: "copilot" | |
| GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} | |
| GH_AW_ENGINE_VERSION: "1.0.60" | |
| GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} | |
| GH_AW_WORKFLOW_ID: "analyze-ci-failure" | |
| GH_AW_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/analyze-ci-failure.md" | |
| 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 | |
| id: setup | |
| uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 | |
| with: | |
| destination: ${{ runner.temp }}/gh-aw/actions | |
| job-name: ${{ github.job }} | |
| trace-id: ${{ needs.activation.outputs.setup-trace-id }} | |
| parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} | |
| env: | |
| GH_AW_SETUP_WORKFLOW_NAME: "Analyze CI Failure" | |
| GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/analyze-ci-failure.lock.yml@${{ github.ref }} | |
| GH_AW_INFO_VERSION: "1.0.60" | |
| GH_AW_INFO_AWF_VERSION: "v0.27.2" | |
| GH_AW_INFO_ENGINE_ID: "copilot" | |
| - name: Download agent output artifact | |
| id: download-agent-output | |
| continue-on-error: true | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: agent | |
| path: /tmp/gh-aw/ | |
| - name: Setup agent output environment variable | |
| id: setup-agent-output-env | |
| if: steps.download-agent-output.outcome == 'success' | |
| run: | | |
| mkdir -p /tmp/gh-aw/ | |
| find "/tmp/gh-aw/" -type f -print | |
| echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" | |
| - name: Configure GH_HOST for enterprise compatibility | |
| id: ghes-host-config | |
| shell: bash | |
| # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. | |
| run: | | |
| # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct | |
| # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. | |
| GH_HOST="${GITHUB_SERVER_URL#https://}" | |
| GH_HOST="${GH_HOST#http://}" | |
| echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" | |
| - name: Process Safe Outputs | |
| id: process_safe_outputs | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| env: | |
| GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} | |
| GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} | |
| GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,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,codeload.github.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,docs.github.com,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,objects.githubusercontent.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,patch-diff.githubusercontent.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" | |
| GITHUB_SERVER_URL: ${{ github.server_url }} | |
| GITHUB_API_URL: ${{ github.api_url }} | |
| GH_AW_SAFE_OUTPUT_JOBS: "{\"publish_data\":\"\",\"rerun_failed_jobs\":\"\"}" | |
| GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" | |
| with: | |
| github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} | |
| script: | | |
| const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); | |
| setupGlobals(core, github, context, exec, io, getOctokit); | |
| const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); | |
| await main(); | |
| - name: Upload Safe Outputs Items | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: safe-outputs-items | |
| path: | | |
| /tmp/gh-aw/safe-output-items.jsonl | |
| /tmp/gh-aw/temporary-id-map.json | |
| if-no-files-found: ignore | |