diff --git a/.github/workflows/gh-aw-pr-buildkite-detective.lock.yml b/.github/workflows/gh-aw-pr-buildkite-detective.lock.yml
new file mode 100644
index 00000000..93526ca6
--- /dev/null
+++ b/.github/workflows/gh-aw-pr-buildkite-detective.lock.yml
@@ -0,0 +1,1337 @@
+#
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw. DO NOT EDIT.
+#
+# To update this file, edit the corresponding .md file and run:
+# gh aw compile
+# Not all edits will cause changes to this file.
+#
+# For more information: https://github.github.com/gh-aw/introduction/overview/
+#
+# Analyze failed Buildkite PR checks and report findings
+#
+# Resolved workflow manifest:
+# Imports:
+# - gh-aw-fragments/elastic-tools.md
+# - gh-aw-fragments/formatting.md
+# - gh-aw-fragments/mcp-pagination.md
+# - gh-aw-fragments/messages-footer.md
+# - gh-aw-fragments/rigor.md
+# - gh-aw-fragments/runtime-setup.md
+# - gh-aw-fragments/safe-output-add-comment.md
+#
+# gh-aw-metadata: {"schema_version":"v1","frontmatter_hash":"49c223e171fc8509c66bde986cd214564d91e0944b4a17ddd78ec1c4e60ed8de"}
+
+name: "PR Buildkite Detective"
+"on":
+ # bots: # Bots processed as bot check in pre-activation job
+ # - ${{ inputs.allowed-bot-users }} # Bots processed as bot check in pre-activation job
+ # roles: # Roles processed as role check in pre-activation job
+ # - admin # Roles processed as role check in pre-activation job
+ # - maintainer # Roles processed as role check in pre-activation job
+ # - write # Roles processed as role check in pre-activation job
+ workflow_call:
+ inputs:
+ additional-instructions:
+ default: ""
+ description: Repo-specific instructions appended to the agent prompt
+ required: false
+ type: string
+ allowed-bot-users:
+ default: github-actions[bot]
+ description: Allowlisted bot actor usernames (comma-separated)
+ required: false
+ type: string
+ buildkite-org:
+ default: elastic
+ description: Buildkite organization slug
+ required: false
+ type: string
+ buildkite-pipeline:
+ default: ""
+ description: Buildkite pipeline slug (optional; auto-discovered from repository if empty)
+ required: false
+ type: string
+ messages-footer:
+ default: ""
+ description: Footer appended to all agent comments and reviews
+ required: false
+ type: string
+ setup-commands:
+ default: ""
+ description: Shell commands to run before the agent starts (dependency install, build, etc.)
+ required: false
+ type: string
+ secrets:
+ BUILDKITE_API_TOKEN:
+ required: true
+ COPILOT_GITHUB_TOKEN:
+ required: true
+
+permissions: {}
+
+concurrency:
+ cancel-in-progress: false
+ group: pr-buildkite-detective-${{ github.event.workflow_run.id }}
+
+run-name: "PR Buildkite Detective"
+
+jobs:
+ activation:
+ needs: pre_activation
+ if: needs.pre_activation.outputs.activated == 'true'
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ outputs:
+ comment_id: ""
+ comment_repo: ""
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Validate context variables
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/validate_context_variables.cjs');
+ await main();
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ sparse-checkout: |
+ .github
+ .agents
+ fetch-depth: 1
+ persist-credentials: false
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "gh-aw-pr-buildkite-detective.lock.yml"
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Create prompt with built-in context
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_EXPR_0EAAB79A: ${{ inputs.buildkite-pipeline }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_EXPR_6908A9DD: ${{ inputs.buildkite-org }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ run: |
+ bash /opt/gh-aw/actions/create_prompt_first.sh
+ cat << 'GH_AW_PROMPT_EOF' > "$GH_AW_PROMPT"
+
+ GH_AW_PROMPT_EOF
+ cat "/opt/gh-aw/prompts/xpia.md" >> "$GH_AW_PROMPT"
+ cat "/opt/gh-aw/prompts/temp_folder_prompt.md" >> "$GH_AW_PROMPT"
+ cat "/opt/gh-aw/prompts/markdown.md" >> "$GH_AW_PROMPT"
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ GitHub API Access Instructions
+
+ The gh CLI is NOT authenticated. Do NOT use gh commands for GitHub operations.
+
+
+ To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls.
+
+ Temporary IDs: Some safe output tools support a temporary ID field (usually named temporary_id) so you can reference newly-created items elsewhere in the SAME agent output (for example, using #aw_abc1 in a later body).
+
+ **IMPORTANT - temporary_id format rules:**
+ - If you DON'T need to reference the item later, OMIT the temporary_id field entirely (it will be auto-generated if needed)
+ - If you DO need cross-references/chaining, you MUST match this EXACT validation regex: /^aw_[A-Za-z0-9]{3,8}$/i
+ - Format: aw_ prefix followed by 3 to 8 alphanumeric characters (A-Z, a-z, 0-9, case-insensitive)
+ - Valid alphanumeric characters: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
+ - INVALID examples: aw_ab (too short), aw_123456789 (too long), aw_test-id (contains hyphen), aw_id_123 (contains underscore)
+ - VALID examples: aw_abc, aw_abc1, aw_Test123, aw_A1B2C3D4, aw_12345678
+ - To generate valid IDs: use 3-8 random alphanumeric characters or omit the field to let the system auto-generate
+
+ Do NOT invent other aw_* formats — downstream steps will reject them with validation errors matching against /^aw_[A-Za-z0-9]{3,8}$/i.
+
+ Discover available tools from the safeoutputs MCP server.
+
+ **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped.
+
+ **Note**: If you made no other safe output tool calls during this workflow execution, call the "noop" tool to provide a status message indicating completion or that no actions were needed.
+
+
+
+ The following GitHub context information is available for this workflow:
+ {{#if __GH_AW_GITHUB_ACTOR__ }}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_REPOSITORY__ }}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_WORKSPACE__ }}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }}
+ - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }}
+ - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }}
+ - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }}
+ - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__
+ {{/if}}
+ {{#if __GH_AW_GITHUB_RUN_ID__ }}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## MCP Servers
+
+ - **`generate_agents_md`** — generates a summary of the repository's structure, conventions, and coding guidelines from AGENTS.md and related files. Call at the start of every workflow to get repo context.
+ - **`search_code`** — grep-style search across public GitHub repositories. Use for finding usage patterns in upstream libraries, reference implementations, or examples in open-source projects. This searches *public GitHub repos*, not the local codebase — if available you can use `grep` and file reading for local code.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## Formatting Guidelines
+
+ - Lead with the most important information — your first sentence should be the key takeaway
+ - Be concise and actionable — no filler or praise
+ - Use `` and `` tags for long sections to keep responses scannable
+ - Wrap branch names and @-references in backticks to avoid pinging users
+ - Include code snippets with file paths and line numbers when referencing the codebase
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## Rigor
+
+ **Silence is better than noise. A false positive wastes a human's time and erodes trust in every future report.**
+
+ - If you claim something is missing or broken, show the exact evidence in the code — file path, line number, and what you observed.
+ - If a conclusion depends on assumptions you haven't confirmed, do not assert it. Verify first; if you cannot verify, do not report.
+ - "I don't know" is better than a wrong answer. `noop` is better than a speculative finding.
+ - It's worth the time to verify now versus guessing and forcing someone else to verify later.
+ - Before filing any issue or opening any PR, re-read your own output as a skeptical reviewer. Ask: "Would a senior engineer on this team find this useful, or would they close it immediately?" If the answer is "close," call `noop` instead.
+ - Only report findings you would confidently defend in a code review. If you feel the need to hedge with "might," "could," or "possibly," the finding is not ready to file.
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## MCP Pagination
+
+ MCP tool responses have a **25,000 token limit**. When responses exceed this limit, the call fails and you must retry with pagination — wasting turns and tokens. Use proactive pagination to stay under the limit.
+
+ ### Recommended `perPage` Values
+
+ - **5-10**: For detailed items (PR diffs, files with patches, issues with comments)
+ - **20-30**: For medium-detail lists (commits, review comments, issue lists)
+ - **50-100**: For simple list operations (branches, labels, tags)
+
+ ### Pagination Pattern
+
+ When you need all results from a paginated API:
+
+ 1. Fetch the first page with a conservative `perPage` value
+ 2. Process the results before fetching the next page
+ 3. Continue fetching pages until you receive fewer results than `perPage` (indicating the last page)
+
+ ### Error Recovery
+
+ If you see an error like:
+ - `MCP tool response exceeds maximum allowed tokens (25000)`
+ - `Response too large for tool [tool_name]`
+
+ Retry the same call with a smaller `perPage` value (halve it).
+
+ ### Tips
+
+ - **Start small**: It's better to make multiple small requests than one that fails
+ - **Fetch incrementally**: Get an overview first, then details for specific items
+ - **Use filters**: Combine `perPage` with state, label, or date filters to reduce result size
+ - **Process as you go**: Don't accumulate all pages before acting — process each batch immediately
+
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## Message Footer
+
+ A footer is automatically appended to all comments and reviews. Do not add your own footer or sign-off — the runtime handles this.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ ## add-comment Limitations
+
+ - **Body**: Max 65,536 characters (including any footer added by gh-aw). Keep well under this limit.
+ - **Mentions**: Max 10 `@` mentions per comment.
+ - **Links**: Max 50 URLs per comment.
+ - **HTML**: Only safe tags allowed (`details`, `summary`, `code`, `pre`, `blockquote`, `table`, `b`, `em`, `strong`, `h1`–`h6`, `hr`, `br`, `li`, `ol`, `ul`, `p`, `sub`, `sup`). Other tags are converted to parentheses.
+ - **URLs**: Only HTTPS URLs to allowed domains. Non-HTTPS and non-allowed domains are redacted.
+ - **Bot triggers**: References like `fixes #123` or `closes #456` are neutralized to prevent unintended issue closures.
+
+ If you exceed 10 mentions or 50 links, the comment will be rejected.
+ GH_AW_PROMPT_EOF
+ cat << 'GH_AW_PROMPT_EOF' >> "$GH_AW_PROMPT"
+ # PR Buildkite Detective
+
+ Assist with failed Buildkite checks for pull requests in __GH_AW_GITHUB_REPOSITORY__. Analyze Buildkite build logs, explain failures, and recommend fixes. This workflow is read-only.
+
+ ## Context
+
+ - **Repository**: __GH_AW_GITHUB_REPOSITORY__
+ - **Workflow Run ID**: __GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID__
+ - **Conclusion**: __GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION__
+ - **Buildkite Organization**: __GH_AW_EXPR_6908A9DD__
+
+ ## Constraints
+
+ - **CAN**: Read files, search code, run tests and commands, query Buildkite via MCP, comment on PRs
+ - **CANNOT**: Push changes, merge PRs, or modify `.github/workflows/`
+
+ ## Instructions
+
+ ### Step 1: Gather Context
+
+ 1. Call `generate_agents_md` to get the repository's coding guidelines and conventions. If this fails, continue without it.
+ 2. Identify the PRs associated with the workflow run using `github.event.workflow_run.pull_requests`. If there are none, call `noop` with message "No pull request associated with workflow run; nothing to do" and stop.
+ 3. For each PR, call `pull_request_read` with method `get` to capture the author, branches, and fork status.
+
+ ### Step 1b: Find the Buildkite Build
+
+ 1. **Resolve the pipeline**: If `__GH_AW_EXPR_0EAAB79A__` is provided, use it. Otherwise, call Buildkite MCP `list_pipelines` for organization `__GH_AW_EXPR_6908A9DD__` and select the pipeline whose slug matches the repository name (extract from `__GH_AW_GITHUB_REPOSITORY__`).
+ 2. **Find the failed build**: Call `list_builds` for the resolved pipeline, filtering by commit SHA `__GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA__`. If no match, select the latest failed build for the pipeline.
+ 3. **Fetch failure evidence**:
+ - Call `get_build` for the selected build.
+ - For each failed job, call `get_job_logs`, `search_logs` (`error|Error|ERROR|failed|Failed|FAILED|panic|exception`), and `tail_logs`.
+ - Call `list_annotations` to capture warnings/errors attached to the build.
+
+ ### Step 2: Analyze
+
+ - Identify the failing job/step and summarize the root cause.
+ - Propose a concrete, minimal fix or remediation plan.
+ - If logs are inconclusive, state what additional data is needed.
+
+ ### Step 3: Respond
+
+ Call `add_comment` on the PR with:
+ - A concise summary of the failure and root cause
+ - The Buildkite build URL and failing job names
+ - The recommended fix or remediation plan
+ - Tests run and their results (if any)
+ - Any follow-up steps required
+
+ __GH_AW_EXPR_49B959F1__
+
+ GH_AW_PROMPT_EOF
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_EXPR_6908A9DD: ${{ inputs.buildkite-org }}
+ GH_AW_EXPR_0EAAB79A: ${{ inputs.buildkite-pipeline }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/interpolate_prompt.cjs');
+ await main();
+ - name: Substitute placeholders
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_EXPR_0EAAB79A: ${{ inputs.buildkite-pipeline }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_EXPR_6908A9DD: ${{ inputs.buildkite-org }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ const substitutePlaceholders = require('/opt/gh-aw/actions/substitute_placeholders.cjs');
+
+ // Call the substitution function
+ return await substitutePlaceholders({
+ file: process.env.GH_AW_PROMPT,
+ substitutions: {
+ GH_AW_EXPR_0EAAB79A: process.env.GH_AW_EXPR_0EAAB79A,
+ GH_AW_EXPR_49B959F1: process.env.GH_AW_EXPR_49B959F1,
+ GH_AW_EXPR_6908A9DD: process.env.GH_AW_EXPR_6908A9DD,
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION: process.env.GH_AW_GITHUB_EVENT_WORKFLOW_RUN_CONCLUSION,
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA: process.env.GH_AW_GITHUB_EVENT_WORKFLOW_RUN_HEAD_SHA,
+ GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID: process.env.GH_AW_GITHUB_EVENT_WORKFLOW_RUN_ID,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED,
+ GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND
+ }
+ });
+ - name: Validate prompt placeholders
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/validate_prompt_placeholders.sh
+ - name: Print prompt
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ run: bash /opt/gh-aw/actions/print_prompt_summary.sh
+ - name: Upload prompt artifact
+ if: success()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: prompt
+ path: /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ issues: read
+ pull-requests: read
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_WORKFLOW_ID_SANITIZED: ghawprbuildkitedetective
+ outputs:
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - if: hashFiles('go.mod') != ''
+ name: Setup Go
+ uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
+ with:
+ cache: true
+ go-version-file: go.mod
+ - if: hashFiles('.python-version') != ''
+ name: Setup Python
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
+ with:
+ python-version-file: .python-version
+ - if: hashFiles('.node-version') != ''
+ name: Setup Node.js (.node-version)
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
+ with:
+ node-version-file: .node-version
+ - if: hashFiles('.node-version') == '' && hashFiles('.nvmrc') != ''
+ name: Setup Node.js (.nvmrc)
+ uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
+ with:
+ node-version-file: .nvmrc
+ - if: hashFiles('.ruby-version') != ''
+ name: Setup Ruby
+ uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1
+ with:
+ bundler-cache: true
+ ruby-version: .ruby-version
+ - id: setup-uv
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ name: Setup uv
+ uses: astral-sh/setup-uv@e58605a9b6da7c637471fab8847a5e5a6b8df081 # v5
+ - env:
+ UV_PATH: ${{ steps.setup-uv.outputs.uv-path }}
+ WORKSPACE: ${{ github.workspace }}
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ name: Expose uv in workspace
+ run: |-
+ set -euo pipefail
+ install_dir="$WORKSPACE/.gh-aw-tools/bin"
+ mkdir -p "$install_dir"
+ cp "$UV_PATH" "$install_dir/uv"
+ chmod +x "$install_dir/uv"
+ echo "$install_dir" >> "$GITHUB_PATH"
+ shell: bash
+ - env:
+ SETUP_COMMANDS: ${{ inputs.setup-commands }}
+ if: ${{ inputs.setup-commands != '' }}
+ name: Repo-specific setup
+ run: eval "$SETUP_COMMANDS"
+
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Checkout PR branch
+ id: checkout-pr
+ if: |
+ github.event.pull_request
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Generate agentic run info
+ id: generate_aw_info
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const fs = require('fs');
+
+ const awInfo = {
+ engine_id: "copilot",
+ engine_name: "GitHub Copilot CLI",
+ model: "gpt-5.3-codex",
+ version: "",
+ agent_version: "0.0.412",
+ workflow_name: "PR Buildkite Detective",
+ experimental: false,
+ supports_tools_allowlist: true,
+ run_id: context.runId,
+ run_number: context.runNumber,
+ run_attempt: process.env.GITHUB_RUN_ATTEMPT,
+ repository: context.repo.owner + '/' + context.repo.repo,
+ ref: context.ref,
+ sha: context.sha,
+ actor: context.actor,
+ event_name: context.eventName,
+ staged: false,
+ allowed_domains: ["agents-md-generator.fastmcp.app","artifacts.elastic.co","cloud.elastic.co","defaults","ela.st","elastic.co","github","mcp.buildkite.com","public-code-search.fastmcp.app","www.elastic.co"],
+ firewall_enabled: true,
+ awf_version: "v0.20.2",
+ awmg_version: "v0.1.4",
+ steps: {
+ firewall: "squid"
+ },
+ created_at: new Date().toISOString()
+ };
+
+ // Write to /tmp/gh-aw directory to avoid inclusion in PR
+ const tmpPath = '/tmp/gh-aw/aw_info.json';
+ fs.writeFileSync(tmpPath, JSON.stringify(awInfo, null, 2));
+ console.log('Generated aw_info.json at:', tmpPath);
+ console.log(JSON.stringify(awInfo, null, 2));
+
+ // Set model as output for reuse in other steps/jobs
+ core.setOutput('model', awInfo.model);
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.412
+ - name: Install awf binary
+ run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.20.2
+ - name: Determine automatic lockdown mode for GitHub MCP Server
+ id: determine-automatic-lockdown
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ with:
+ script: |
+ const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.20.2 ghcr.io/github/gh-aw-firewall/api-proxy:0.20.2 ghcr.io/github/gh-aw-firewall/squid:0.20.2 ghcr.io/github/gh-aw-mcpg:v0.1.4 ghcr.io/github/github-mcp-server:v0.31.0 node:lts-alpine
+ - name: Write Safe Outputs Config
+ run: |
+ mkdir -p /opt/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/safeoutputs
+ mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
+ cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF'
+ {"add_comment":{"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_EOF
+ cat > /opt/gh-aw/safeoutputs/tools.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_EOF'
+ [
+ {
+ "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. IMPORTANT: Comments are subject to validation constraints enforced by the MCP server - maximum 65536 characters for the complete comment (including footer which is added automatically), 10 mentions (@username), and 50 links. Exceeding these limits will result in an immediate error with specific guidance. NOTE: By default, this tool requires discussions:write permission. If your GitHub App lacks Discussions permission, set 'discussions: false' in the workflow's safe-outputs.add-comment configuration to exclude this permission. CONSTRAINTS: Maximum 1 comment(s) can be added.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "body": {
+ "description": "The comment text in Markdown format. This is the 'body' field - do not use 'comment_body' or other variations. Provide helpful, relevant information that adds value to the conversation. CONSTRAINTS: The complete comment (your body text + automatically added footer) must not exceed 65536 characters total. Maximum 10 mentions (@username), maximum 50 links (http/https URLs). A footer (~200-500 characters) is automatically appended with workflow attribution, so leave adequate space. If these limits are exceeded, the tool call will fail with a detailed error message indicating which constraint was violated.",
+ "type": "string"
+ },
+ "item_number": {
+ "description": "The issue, pull request, or discussion number to comment on. This is the numeric ID from the GitHub URL (e.g., 123 in github.com/owner/repo/issues/123). If omitted, the tool will attempt to resolve the target from the current workflow context (triggering issue, PR, or discussion).",
+ "type": "number"
+ }
+ },
+ "required": [
+ "body"
+ ],
+ "type": "object"
+ },
+ "name": "add_comment"
+ },
+ {
+ "description": "Report that a tool or capability needed to complete the task is not available, or share any information you deem important about missing functionality or limitations. Use this when you cannot accomplish what was requested because the required functionality is missing or access is restricted.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this tool is needed or what information you want to share about the limitation (max 256 characters).",
+ "type": "string"
+ },
+ "tool": {
+ "description": "Optional: Name or description of the missing tool or capability (max 128 characters). Be specific about what functionality is needed.",
+ "type": "string"
+ }
+ },
+ "required": [
+ "reason"
+ ],
+ "type": "object"
+ },
+ "name": "missing_tool"
+ },
+ {
+ "description": "Log a transparency message when no significant actions are needed. Use this to confirm workflow completion and provide visibility when analysis is complete but no changes or outputs are required (e.g., 'No issues found', 'All checks passed'). This ensures the workflow produces human-visible output even when no other actions are taken.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "message": {
+ "description": "Status or completion message to log. Should explain what was analyzed and the outcome (e.g., 'Code review complete - no issues found', 'Analysis complete - all tests passing').",
+ "type": "string"
+ }
+ },
+ "required": [
+ "message"
+ ],
+ "type": "object"
+ },
+ "name": "noop"
+ },
+ {
+ "description": "Report that data or information needed to complete the task is not available. Use this when you cannot accomplish what was requested because required data, context, or information is missing.",
+ "inputSchema": {
+ "additionalProperties": false,
+ "properties": {
+ "alternatives": {
+ "description": "Any workarounds, manual steps, or alternative approaches the user could take (max 256 characters).",
+ "type": "string"
+ },
+ "context": {
+ "description": "Additional context about the missing data or where it should come from (max 256 characters).",
+ "type": "string"
+ },
+ "data_type": {
+ "description": "Type or description of the missing data or information (max 128 characters). Be specific about what data is needed.",
+ "type": "string"
+ },
+ "reason": {
+ "description": "Explanation of why this data is needed to complete the task (max 256 characters).",
+ "type": "string"
+ }
+ },
+ "required": [],
+ "type": "object"
+ },
+ "name": "missing_data"
+ }
+ ]
+ GH_AW_SAFE_OUTPUTS_TOOLS_EOF
+ cat > /opt/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_EOF'
+ {
+ "add_comment": {
+ "defaultMax": 1,
+ "fields": {
+ "body": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ },
+ "item_number": {
+ "issueOrPRNumber": true
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ }
+ }
+ GH_AW_SAFE_OUTPUTS_VALIDATION_EOF
+ - name: Generate Safe Outputs MCP Server Config
+ id: safe-outputs-config
+ run: |
+ # Generate a secure random API key (360 bits of entropy, 40+ chars)
+ # Mask immediately to prevent timing vulnerabilities
+ API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${API_KEY}"
+
+ PORT=3001
+
+ # Set outputs for next steps
+ {
+ echo "safe_outputs_api_key=${API_KEY}"
+ echo "safe_outputs_port=${PORT}"
+ } >> "$GITHUB_OUTPUT"
+
+ echo "Safe Outputs MCP server will run on port ${PORT}"
+
+ - name: Start Safe Outputs MCP HTTP Server
+ id: safe-outputs-start
+ env:
+ DEBUG: '*'
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }}
+ GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json
+ GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ run: |
+ # Environment variables are set above to prevent template injection
+ export DEBUG
+ export GH_AW_SAFE_OUTPUTS_PORT
+ export GH_AW_SAFE_OUTPUTS_API_KEY
+ export GH_AW_SAFE_OUTPUTS_TOOLS_PATH
+ export GH_AW_SAFE_OUTPUTS_CONFIG_PATH
+ export GH_AW_MCP_LOG_DIR
+
+ bash /opt/gh-aw/actions/start_safe_outputs_server.sh
+
+ - name: Start MCP Gateway
+ id: start-mcp-gateway
+ env:
+ BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }}
+ GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }}
+ GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ run: |
+ set -eo pipefail
+ mkdir -p /tmp/gh-aw/mcp-config
+
+ # Export gateway environment variables for MCP config and gateway script
+ export MCP_GATEWAY_PORT="80"
+ export MCP_GATEWAY_DOMAIN="host.docker.internal"
+ MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=')
+ echo "::add-mask::${MCP_GATEWAY_API_KEY}"
+ export MCP_GATEWAY_API_KEY
+ export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
+ mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
+ export DEBUG="*"
+
+ export GH_AW_ENGINE="copilot"
+ export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e BUILDKITE_API_TOKEN -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.1.4'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "agents-md-generator": {
+ "type": "http",
+ "url": "https://agents-md-generator.fastmcp.app/mcp",
+ "tools": [
+ "generate_agents_md"
+ ]
+ },
+ "buildkite": {
+ "type": "http",
+ "url": "https://mcp.buildkite.com/mcp/readonly",
+ "headers": {
+ "Authorization": "Bearer \${BUILDKITE_API_TOKEN}"
+ },
+ "tools": [
+ "*"
+ ],
+ "env": {
+ "BUILDKITE_API_TOKEN": "\${BUILDKITE_API_TOKEN}"
+ }
+ },
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.31.0",
+ "env": {
+ "GITHUB_LOCKDOWN_MODE": "$GITHUB_MCP_LOCKDOWN",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "repos,issues,pull_requests,search"
+ }
+ },
+ "public-code-search": {
+ "type": "http",
+ "url": "https://public-code-search.fastmcp.app/mcp",
+ "tools": [
+ "search_code"
+ ]
+ },
+ "safeoutputs": {
+ "type": "http",
+ "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT",
+ "headers": {
+ "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}"
+ }
+ }
+ },
+ "gateway": {
+ "port": $MCP_GATEWAY_PORT,
+ "domain": "${MCP_GATEWAY_DOMAIN}",
+ "apiKey": "${MCP_GATEWAY_API_KEY}",
+ "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}"
+ }
+ }
+ GH_AW_MCP_CONFIG_EOF
+ - name: Generate workflow overview
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { generateWorkflowOverview } = require('/opt/gh-aw/actions/generate_workflow_overview.cjs');
+ await generateWorkflowOverview(core);
+ - name: Download prompt artifact
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: prompt
+ path: /tmp/gh-aw/aw-prompts
+ - name: Clean git credentials
+ run: bash /opt/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 30
+ run: |
+ set -o pipefail
+ sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains '*.githubusercontent.com,agents-md-generator.fastmcp.app,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,artifacts.elastic.co,azure.archive.ubuntu.com,cloud.elastic.co,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,ela.st,elastic.co,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,mcp.buildkite.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,ppa.launchpad.net,public-code-search.fastmcp.app,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.elastic.co' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.20.2 --skip-pull --enable-api-proxy \
+ -- /bin/bash -c '/usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --add-dir "${GITHUB_WORKSPACE}" --disable-builtin-mcps --model gpt-5.3-codex --allow-all-tools --allow-all-paths --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: Copy Copilot session state files to logs
+ if: always()
+ continue-on-error: true
+ run: |
+ # Copy Copilot session state files to logs folder for artifact collection
+ # This ensures they are in /tmp/gh-aw/ where secret redaction can scan them
+ SESSION_STATE_DIR="$HOME/.copilot/session-state"
+ LOGS_DIR="/tmp/gh-aw/sandbox/agent/logs"
+
+ if [ -d "$SESSION_STATE_DIR" ]; then
+ echo "Copying Copilot session state files from $SESSION_STATE_DIR to $LOGS_DIR"
+ mkdir -p "$LOGS_DIR"
+ cp -v "$SESSION_STATE_DIR"/*.jsonl "$LOGS_DIR/" 2>/dev/null || true
+ echo "Session state files copied successfully"
+ else
+ echo "No session-state directory found at $SESSION_STATE_DIR"
+ fi
+ - name: Stop MCP Gateway
+ if: always()
+ continue-on-error: true
+ env:
+ MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
+ MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }}
+ GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
+ run: |
+ bash /opt/gh-aw/actions/stop_mcp_gateway.sh "$GATEWAY_PID"
+ - name: Redact secrets in logs
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'BUILDKITE_API_TOKEN,COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Upload Safe Outputs
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: safe-output
+ path: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ if-no-files-found: warn
+ - name: Ingest agent output
+ id: collect_output
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
+ GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,agents-md-generator.fastmcp.app,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,artifacts.elastic.co,azure.archive.ubuntu.com,cloud.elastic.co,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,ela.st,elastic.co,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,mcp.buildkite.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,ppa.launchpad.net,public-code-search.fastmcp.app,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.elastic.co"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/collect_ndjson_output.cjs');
+ await main();
+ - name: Upload sanitized agent output
+ if: always() && env.GH_AW_AGENT_OUTPUT
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent-output
+ path: ${{ env.GH_AW_AGENT_OUTPUT }}
+ if-no-files-found: warn
+ - name: Upload engine output files
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent_outputs
+ path: |
+ /tmp/gh-aw/sandbox/agent/logs/
+ /tmp/gh-aw/redacted-urls.log
+ if-no-files-found: ignore
+ - name: Parse agent logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_copilot_log.cjs');
+ await main();
+ - name: Parse MCP Gateway logs for step summary
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_mcp_gateway_log.cjs');
+ await main();
+ - name: Print firewall logs
+ if: always()
+ continue-on-error: true
+ env:
+ AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs
+ run: |
+ # Fix permissions on firewall logs so they can be uploaded as artifacts
+ # AWF runs with sudo, creating files owned by root
+ sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true
+ # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step)
+ if command -v awf &> /dev/null; then
+ awf logs summary | tee -a "$GITHUB_STEP_SUMMARY"
+ else
+ echo 'AWF binary not installed, skipping firewall log summary'
+ fi
+ - name: Upload agent artifacts
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: agent-artifacts
+ path: |
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/mcp-logs/
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/agent-stdio.log
+ /tmp/gh-aw/agent/
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ if: (always()) && (needs.agent.result != 'skipped')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ outputs:
+ noop_message: ${{ steps.noop.outputs.noop_message }}
+ tools_reported: ${{ steps.missing_tool.outputs.tools_reported }}
+ total_count: ${{ steps.missing_tool.outputs.total_count }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: 1
+ GH_AW_WORKFLOW_NAME: "PR Buildkite Detective"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/noop.cjs');
+ await main();
+ - name: Record Missing Tool
+ id: missing_tool
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "PR Buildkite Detective"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "PR Buildkite Detective"
+ 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: "gh-aw-pr-buildkite-detective"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.agent.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[What is this?](https://ela.st/github-ai-tools) | [From workflow: {workflow_name}]({run_url})\\n\\nGive us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.' }}\"}"
+ GH_AW_GROUP_REPORTS: "false"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_agent_failure.cjs');
+ await main();
+ - name: Handle No-Op Message
+ id: handle_noop_message
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "PR Buildkite Detective"
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_NOOP_MESSAGE: ${{ steps.noop.outputs.noop_message }}
+ GH_AW_NOOP_REPORT_AS_ISSUE: "true"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+
+ detection:
+ needs: agent
+ if: needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true'
+ runs-on: ubuntu-latest
+ permissions: {}
+ concurrency:
+ group: "gh-aw-copilot-${{ github.workflow }}"
+ timeout-minutes: 10
+ outputs:
+ success: ${{ steps.parse_results.outputs.success }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent artifacts
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-artifacts
+ path: /tmp/gh-aw/threat-detection/
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/threat-detection/
+ - name: Print agent output types
+ env:
+ AGENT_OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
+ run: |
+ echo "Agent output-types: $AGENT_OUTPUT_TYPES"
+ - name: Setup threat detection
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "PR Buildkite Detective"
+ WORKFLOW_DESCRIPTION: "Analyze failed Buildkite PR checks and report findings"
+ HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/setup_threat_detection.cjs');
+ await main();
+ - name: Ensure threat-detection directory and log
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection
+ touch /tmp/gh-aw/threat-detection/detection.log
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: /opt/gh-aw/actions/validate_multi_secret.sh COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default
+ env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ - name: Install GitHub Copilot CLI
+ run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.412
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool shell(cat)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(wc)
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ COPILOT_CLI_INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"
+ mkdir -p /tmp/
+ mkdir -p /tmp/gh-aw/
+ mkdir -p /tmp/gh-aw/agent/
+ mkdir -p /tmp/gh-aw/sandbox/agent/logs/
+ copilot --add-dir /tmp/ --add-dir /tmp/gh-aw/ --add-dir /tmp/gh-aw/agent/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --model gpt-5.3-codex --allow-tool 'shell(cat)' --allow-tool 'shell(grep)' --allow-tool 'shell(head)' --allow-tool 'shell(jq)' --allow-tool 'shell(ls)' --allow-tool 'shell(tail)' --allow-tool 'shell(wc)' --share /tmp/gh-aw/sandbox/agent/logs/conversation.md --prompt "$COPILOT_CLI_INSTRUCTION" 2>&1 | tee /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GITHUB_HEAD_REF: ${{ github.head_ref }}
+ GITHUB_REF_NAME: ${{ github.ref_name }}
+ GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
+ GITHUB_WORKSPACE: ${{ github.workspace }}
+ XDG_CONFIG_HOME: /home/runner
+ - name: Parse threat detection results
+ id: parse_results
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ with:
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/parse_threat_detection_results.cjs');
+ await main();
+ - name: Upload threat detection log
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: threat-detection.log
+ path: /tmp/gh-aw/threat-detection/detection.log
+ if-no-files-found: ignore
+
+ pre_activation:
+ runs-on: ubuntu-slim
+ outputs:
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Check team membership for workflow
+ id: check_membership
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_REQUIRED_ROLES: admin,maintainer,write
+ GH_AW_ALLOWED_BOTS: ${{ inputs.allowed-bot-users }}
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/check_membership.cjs');
+ await main();
+
+ safe_outputs:
+ needs:
+ - agent
+ - detection
+ if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (needs.detection.outputs.success == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ timeout-minutes: 15
+ env:
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_MODEL: "gpt-5.3-codex"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[What is this?](https://ela.st/github-ai-tools) | [From workflow: {workflow_name}]({run_url})\\n\\nGive us feedback! React with 🚀 if perfect, 👍 if helpful, 👎 if not.' }}\"}"
+ GH_AW_WORKFLOW_ID: "gh-aw-pr-buildkite-detective"
+ GH_AW_WORKFLOW_NAME: "PR Buildkite Detective"
+ outputs:
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw/actions/setup@da463a765059a5eed4cc345a003ecfc71c45ec49 # v0.47.4
+ with:
+ destination: /opt/gh-aw/actions
+ - name: Download agent output artifact
+ continue-on-error: true
+ uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
+ with:
+ name: agent-output
+ path: /tmp/gh-aw/safeoutputs/
+ - name: Setup agent output environment variable
+ run: |
+ mkdir -p /tmp/gh-aw/safeoutputs/
+ find "/tmp/gh-aw/safeoutputs/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/safeoutputs/agent_output.json" >> "$GITHUB_ENV"
+ - name: Process Safe Outputs
+ id: process_safe_outputs
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1},\"missing_data\":{},\"missing_tool\":{}}"
+ with:
+ github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('/opt/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Upload safe output items manifest
+ if: always()
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
+ with:
+ name: safe-output-items
+ path: /tmp/safe-output-items.jsonl
+ if-no-files-found: warn
+
diff --git a/.github/workflows/gh-aw-pr-buildkite-detective.md b/.github/workflows/gh-aw-pr-buildkite-detective.md
new file mode 100644
index 00000000..82d50a30
--- /dev/null
+++ b/.github/workflows/gh-aw-pr-buildkite-detective.md
@@ -0,0 +1,170 @@
+---
+description: "Analyze failed Buildkite PR checks and report findings"
+imports:
+ - gh-aw-fragments/elastic-tools.md
+ - gh-aw-fragments/runtime-setup.md
+ - gh-aw-fragments/formatting.md
+ - gh-aw-fragments/rigor.md
+ - gh-aw-fragments/mcp-pagination.md
+ - gh-aw-fragments/messages-footer.md
+ - gh-aw-fragments/safe-output-add-comment.md
+engine:
+ id: copilot
+ model: gpt-5.3-codex
+on:
+ workflow_call:
+ inputs:
+ additional-instructions:
+ description: "Repo-specific instructions appended to the agent prompt"
+ type: string
+ required: false
+ default: ""
+ setup-commands:
+ description: "Shell commands to run before the agent starts (dependency install, build, etc.)"
+ type: string
+ required: false
+ default: ""
+ allowed-bot-users:
+ description: "Allowlisted bot actor usernames (comma-separated)"
+ type: string
+ required: false
+ default: "github-actions[bot]"
+ messages-footer:
+ description: "Footer appended to all agent comments and reviews"
+ type: string
+ required: false
+ default: ""
+ buildkite-org:
+ description: "Buildkite organization slug"
+ type: string
+ required: false
+ default: "elastic"
+ buildkite-pipeline:
+ description: "Buildkite pipeline slug (optional; auto-discovered from repository if empty)"
+ type: string
+ required: false
+ default: ""
+ secrets:
+ COPILOT_GITHUB_TOKEN:
+ required: true
+ BUILDKITE_API_TOKEN:
+ required: true
+ roles: [admin, maintainer, write]
+ bots:
+ - "${{ inputs.allowed-bot-users }}"
+concurrency:
+ group: pr-buildkite-detective-${{ github.event.workflow_run.id }}
+ cancel-in-progress: false
+permissions:
+ contents: read
+ issues: read
+ pull-requests: read
+tools:
+ github:
+ toolsets: [repos, issues, pull_requests, search]
+ bash: true
+ web-fetch:
+mcp-servers:
+ buildkite:
+ url: "https://mcp.buildkite.com/mcp/readonly"
+ headers:
+ Authorization: "Bearer ${{ secrets.BUILDKITE_API_TOKEN }}"
+network:
+ allowed:
+ - defaults
+ - github
+ - "mcp.buildkite.com"
+strict: false
+timeout-minutes: 30
+steps:
+ - name: Repo-specific setup
+ if: ${{ inputs.setup-commands != '' }}
+ env:
+ SETUP_COMMANDS: ${{ inputs.setup-commands }}
+ run: eval "$SETUP_COMMANDS"
+---
+
+# PR Buildkite Detective
+
+Analyze failed Buildkite CI builds for pull requests in ${{ github.repository }}. Identify root causes from build logs, trace failures to source code, and provide actionable fix recommendations via PR comments. This workflow is read-only.
+
+## Context
+
+- **Repository**: ${{ github.repository }}
+- **Workflow Run ID**: ${{ github.event.workflow_run.id }}
+- **Conclusion**: ${{ github.event.workflow_run.conclusion }}
+- **Commit SHA**: ${{ github.event.workflow_run.head_sha }}
+- **Buildkite Organization**: ${{ inputs.buildkite-org }}
+
+## Constraints
+
+- **CAN**: Read files, search code, run tests and commands, query Buildkite via MCP, comment on PRs
+- **CANNOT**: Push changes, merge PRs, or modify `.github/workflows/`
+
+## Investigation Tools
+
+Use the right tool for each task:
+
+- **Buildkite MCP** (`list_pipelines`, `list_builds`, `get_build`, `get_job_logs`, `search_logs`, `tail_logs`, `list_annotations`): Query build information, job logs, and annotations
+- **`search_code`**: Search code in *other* public GitHub repositories — use for finding upstream API changes, reference implementations, or migration guides. Use `grep` and file reading for the local codebase.
+- **`web-fetch`**: Fetch documentation pages, changelogs, or API references for libraries and tools involved in the failure
+- **`bash`**: Run tests locally to verify your analysis, reproduce failures, or check dependency versions
+
+## Failure Categories
+
+Classify each failure to guide your investigation:
+
+- **Code bug**: Logic error, syntax error, type mismatch, nil/null dereference — trace to the specific source file and line
+- **Test failure**: Assertion mismatch, test timeout, flaky test — check if the test itself is wrong or if the code under test changed
+- **Dependency issue**: Missing package, version conflict, lockfile drift, network fetch failure — check dependency files and lockfiles
+- **Infrastructure**: Resource exhaustion, service unavailability, timeout, Docker pull failure — often transient; recommend retry if so
+- **Configuration**: Invalid settings, missing secrets/env vars, incorrect paths — check CI config, environment setup, and workflow definitions
+
+## Instructions
+
+### Step 1: Gather Context
+
+1. Call `generate_agents_md` to get the repository's coding guidelines and conventions. If this fails, continue without it.
+2. Identify the PRs associated with the workflow run using `github.event.workflow_run.pull_requests`. If there are none, call `noop` with message "No pull request associated with workflow run; nothing to do" and stop.
+3. For each PR, call `pull_request_read` with method `get` to capture the author, branches, and fork status.
+
+### Step 2: Find the Buildkite Build
+
+1. **Resolve the pipeline**: If `${{ inputs.buildkite-pipeline }}` is provided, use it. Otherwise, call `list_pipelines` for organization `${{ inputs.buildkite-org }}` and find the pipeline whose slug matches the repository name (extract the repo name from `${{ github.repository }}`). If multiple pipelines match, prefer an exact slug match.
+2. **Find the failed build**: Call `list_builds` for the resolved pipeline, filtering by commit SHA `${{ github.event.workflow_run.head_sha }}`. If no match by SHA, use the PR's head branch (from the `pull_request_read` response in Step 1) to filter builds and select the most recent failed one.
+3. **Collect failure evidence**:
+ - Call `get_build` for the matched build to get overall status and job list.
+ - For each **failed** job:
+ - `get_job_logs` — retrieve the full log
+ - `search_logs` with patterns: `error|Error|ERROR`, `failed|Failed|FAILED`, `panic|exception|traceback`
+ - `tail_logs` — get the last 100 lines (often contains the final error and exit code)
+ - Call `list_annotations` to capture any warnings, errors, or context the pipeline attached to the build.
+
+### Step 3: Analyze
+
+1. **Identify the failure**: Which job(s) and step(s) failed? What is the specific error message or stack trace?
+2. **Trace to source code**: Use `grep` and file reading to find the relevant source files. Check recent changes in the PR diff that may have introduced the failure.
+3. **Classify the failure**: Use the failure categories above to determine the type. This guides your fix recommendation.
+4. **Research if needed**: If the error involves an external library, API, or tool, use `web-fetch` to check documentation or changelogs for known issues, breaking changes, or migration guides.
+5. **Propose a fix**: Provide a concrete, minimal fix or remediation plan. If you can run tests locally to verify your theory, do so.
+6. **Handle inconclusive cases**: If logs are insufficient to determine root cause, state exactly what additional data is needed and suggest next steps the author can take.
+
+### Step 4: Respond
+
+Call `add_comment` on the PR with the following structure:
+
+**Build**: Link to the Buildkite build
+
+**What failed**: Which job(s) and step(s) failed
+
+**Error**: The key error message(s) or stack trace
+
+**Root cause**: What caused the failure and why (with file paths and line numbers where applicable)
+
+**Recommended fix**: Specific steps to resolve, with code snippets if applicable
+
+**Verification**: Tests you ran locally (if any) and their results
+
+Use `` blocks for long log excerpts or stack traces to keep the comment scannable.
+
+${{ inputs.additional-instructions }}
diff --git a/docs/workflows/gh-agent-workflows.md b/docs/workflows/gh-agent-workflows.md
index b1618e53..9e0177ff 100644
--- a/docs/workflows/gh-agent-workflows.md
+++ b/docs/workflows/gh-agent-workflows.md
@@ -32,6 +32,7 @@ Each workflow directory contains an `example.yml` starter and a README for trigg
| [Issue Triage (with PR)](gh-agent-workflows/issue-triage-pr.md) | New issues | Triage plus automatic draft PR for straightforward fixes |
| [Mention in Issue](gh-agent-workflows/mention-in-issue.md) | `/ai` in issues | Answer questions, debug, create PRs |
| [Mention in PR](gh-agent-workflows/mention-in-pr.md) | `/ai` in PRs | Review, fix code, push changes |
+| [PR Buildkite Detective](gh-agent-workflows/pr-buildkite-detective.md) | Failed PR checks | Diagnose Buildkite failures and recommend fixes |
| [PR CI Detective](gh-agent-workflows/pr-ci-detective.md) | Failed PR checks | Diagnose failures and recommend fixes |
| [PR CI Fixer](gh-agent-workflows/pr-ci-fixer.md) | Manual (workflow_dispatch) | Opt-in fixes for failed PR checks |
| [PR Checks Fix](gh-agent-workflows/pr-checks-fix.md) | Failed PR checks | Analyze and push fixes for failed PR checks |
@@ -75,6 +76,8 @@ UI path: Settings → Secrets and variables → Actions → New repository secre
See the upstream [gh-aw auth docs](https://github.com/github/gh-aw/blob/main/docs/src/content/docs/reference/auth.mdx) for canonical steps.
+Some workflows require additional provider-specific secrets (for example, `PR Buildkite Detective` requires `BUILDKITE_API_TOKEN`).
+
## Agentic maintenance workflow required
Any workflow that uses safe-outputs with `expires` (create-issue, create-pull-request, create-discussion) requires the `agentics-maintenance` workflow so expired items are closed automatically. Install it once per repo:
diff --git a/gh-agent-workflows/pr-buildkite-detective/README.md b/gh-agent-workflows/pr-buildkite-detective/README.md
new file mode 100644
index 00000000..0e177d7b
--- /dev/null
+++ b/gh-agent-workflows/pr-buildkite-detective/README.md
@@ -0,0 +1,42 @@
+# PR Buildkite Detective
+
+Analyze failed PR checks backed by Buildkite and report findings (read-only).
+
+## How it works
+
+Triggered automatically when a CI workflow fails on a PR. Looks up the related Buildkite build via MCP, analyzes failed jobs/logs/annotations, and posts a comment with root cause and recommended fixes. Read-only — never pushes changes.
+
+## Quick Install
+
+````bash
+mkdir -p .github/workflows && curl -sL \
+ https://raw.githubusercontent.com/elastic/ai-github-actions/v0/gh-agent-workflows/pr-buildkite-detective/example.yml \
+ -o .github/workflows/pr-buildkite-detective.yml
+````
+
+See [example.yml](example.yml) for the full workflow file.
+
+## Trigger
+
+| Event | Types | Condition |
+| --- | --- | --- |
+| `workflow_run` | `completed` | CI workflow failed and the run is associated with a PR |
+
+## Inputs
+
+| Input | Description | Required | Default |
+| --- | --- | --- | --- |
+| `additional-instructions` | Repo-specific instructions appended to the agent prompt | No | `""` |
+| `setup-commands` | Shell commands run before the agent starts | No | `""` |
+| `allowed-bot-users` | Allowlisted bot actor usernames (comma-separated) | No | `github-actions[bot]` |
+| `buildkite-org` | Buildkite organization slug | No | `elastic` |
+| `buildkite-pipeline` | Buildkite pipeline slug (auto-discovered if not provided) | No | `""` |
+
+## Required Secrets
+
+- `COPILOT_GITHUB_TOKEN`
+- `BUILDKITE_API_TOKEN`
+
+## Safe Outputs
+
+- `add-comment` — post a comment explaining the failure (max 3)
diff --git a/gh-agent-workflows/pr-buildkite-detective/example.yml b/gh-agent-workflows/pr-buildkite-detective/example.yml
new file mode 100644
index 00000000..fe6c6d63
--- /dev/null
+++ b/gh-agent-workflows/pr-buildkite-detective/example.yml
@@ -0,0 +1,25 @@
+name: PR Buildkite Detective
+on:
+ workflow_run:
+ workflows: ["CI", "Build", "Test"]
+ types: [completed]
+
+permissions:
+ actions: read
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write # required by gh-aw compiler for add-comment (github/gh-aw#16673)
+
+jobs:
+ run:
+ if: >-
+ github.event.workflow_run.conclusion == 'failure' &&
+ toJSON(github.event.workflow_run.pull_requests) != '[]'
+ uses: elastic/ai-github-actions/.github/workflows/gh-aw-pr-buildkite-detective.lock.yml@v0
+ with:
+ buildkite-org: "elastic"
+ # buildkite-pipeline: "your-pipeline" # auto-discovered if omitted
+ secrets:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}