diff --git a/.github/workflows/gh-aw-issue-triage.lock.yml b/.github/workflows/gh-aw-issue-triage.lock.yml
new file mode 100644
index 0000000..d0bbf63
--- /dev/null
+++ b/.github/workflows/gh-aw-issue-triage.lock.yml
@@ -0,0 +1,1298 @@
+# ___ _ _
+# / _ \ | | (_)
+# | |_| | __ _ ___ _ __ | |_ _ ___
+# | _ |/ _` |/ _ \ '_ \| __| |/ __|
+# | | | | (_| | __/ | | | |_| | (__
+# \_| |_/\__, |\___|_| |_|\__|_|\___|
+# __/ |
+# _ _ |___/
+# | | | | / _| |
+# | | | | ___ _ __ _ __| |_| | _____ ____
+# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
+# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
+# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
+#
+# This file was automatically generated by gh-aw (v0.65.5). 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/
+#
+# Triages issues by analyzing content and applying the appropriate team label. Triggered by typing /triage on an issue comment, or manually via workflow_dispatch.
+#
+# Resolved workflow manifest:
+# Imports:
+# - gh-aw-fragments/docs-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-metadata: {"schema_version":"v3","frontmatter_hash":"aceb826e36b0d4e514d6cc22f345beb4bb7ee102149f98bebb844c218ef9030a","compiler_version":"v0.65.5","strict":true,"agent_id":"copilot"}
+
+name: "Issue Triage Agent"
+"on":
+ issue_comment:
+ types:
+ - created
+ - edited
+ issues:
+ types:
+ - opened
+ - edited
+ - reopened
+ # 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 — team mapping, label rules, CODEOWNERS paths
+ required: false
+ type: string
+ messages-footer:
+ default: ""
+ description: Footer appended to all agent comments
+ required: false
+ type: string
+ setup-commands:
+ default: ""
+ description: Shell commands to run before the agent starts
+ required: false
+ type: string
+ secrets:
+ COPILOT_GITHUB_TOKEN:
+ required: true
+ workflow_dispatch:
+ inputs:
+ aw_context:
+ default: ""
+ description: Agent caller context (used internally by Agentic Workflows).
+ required: false
+ type: string
+
+permissions: {}
+
+concurrency:
+ cancel-in-progress: true
+ group: issue-triage
+
+run-name: "Issue Triage Agent"
+
+jobs:
+ activation:
+ needs: pre_activation
+ if: "needs.pre_activation.outputs.activated == 'true' && ((github.event_name == 'issue_comment' || github.event_name == 'issues') && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/triage ') || startsWith(github.event.issue.body, '/triage\n') || github.event.issue.body == '/triage') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/triage ') || startsWith(github.event.comment.body, '/triage\n') || github.event.comment.body == '/triage') && github.event.issue.pull_request == null) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'issues')))"
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ discussions: write
+ issues: write
+ pull-requests: write
+ outputs:
+ artifact_prefix: ${{ steps.artifact-prefix.outputs.prefix }}
+ body: ${{ steps.sanitized.outputs.body }}
+ comment_id: ${{ steps.add-comment.outputs.comment-id }}
+ comment_repo: ${{ steps.add-comment.outputs.comment-repo }}
+ comment_url: ${{ steps.add-comment.outputs.comment-url }}
+ lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
+ model: ${{ steps.generate_aw_info.outputs.model }}
+ secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }}
+ slash_command: ${{ needs.pre_activation.outputs.matched_command }}
+ target_ref: ${{ steps.resolve-host-repo.outputs.target_ref }}
+ target_repo: ${{ steps.resolve-host-repo.outputs.target_repo }}
+ target_repo_name: ${{ steps.resolve-host-repo.outputs.target_repo_name }}
+ text: ${{ steps.sanitized.outputs.text }}
+ title: ${{ steps.sanitized.outputs.title }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Resolve host repo for activation checkout
+ id: resolve-host-repo
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/resolve_host_repo.cjs');
+ await main();
+ - name: Compute artifact prefix
+ id: artifact-prefix
+ env:
+ INPUTS_JSON: ${{ toJSON(inputs) }}
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/compute_artifact_prefix.sh
+ - 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 || 'auto' }}
+ GH_AW_INFO_VERSION: "latest"
+ GH_AW_INFO_AGENT_VERSION: "latest"
+ GH_AW_INFO_CLI_VERSION: "v0.65.5"
+ GH_AW_INFO_WORKFLOW_NAME: "Issue Triage Agent"
+ GH_AW_INFO_EXPERIMENTAL: "false"
+ GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
+ GH_AW_INFO_STAGED: "false"
+ GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","github","www.elastic.co"]'
+ GH_AW_INFO_FIREWALL_ENABLED: "true"
+ GH_AW_INFO_AWF_VERSION: "v0.25.10"
+ GH_AW_INFO_AWMG_VERSION: ""
+ GH_AW_INFO_FIREWALL_TYPE: "squid"
+ GH_AW_COMPILED_STRICT: "true"
+ GH_AW_INFO_TARGET_REPO: ${{ steps.resolve-host-repo.outputs.target_repo }}
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/generate_aw_info.cjs');
+ await main(core, context);
+ - name: Add eyes reaction for immediate feedback
+ id: react
+ if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_REACTION: "eyes"
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ 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/add_reaction.cjs');
+ await main();
+ - name: Validate COPILOT_GITHUB_TOKEN secret
+ id: validate-secret
+ run: ${RUNNER_TEMP}/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: Cross-repo setup guidance
+ if: failure() && steps.resolve-host-repo.outputs.target_repo != github.repository
+ run: |
+ echo "::error::COPILOT_GITHUB_TOKEN must be configured in the CALLER repository's secrets."
+ echo "::error::For cross-repo workflow_call, secrets must be set in the repository that triggers the workflow."
+ echo "::error::See: https://github.github.com/gh-aw/patterns/central-repo-ops/#cross-repo-setup"
+ - name: Checkout .github and .agents folders
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ repository: ${{ steps.resolve-host-repo.outputs.target_repo }}
+ ref: ${{ steps.resolve-host-repo.outputs.target_ref }}
+ sparse-checkout: |
+ .github
+ .agents
+ sparse-checkout-cone-mode: true
+ fetch-depth: 1
+ - name: Check workflow file timestamps
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_FILE: "gh-aw-issue-triage.lock.yml"
+ 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/check_workflow_timestamp_api.cjs');
+ await main();
+ - name: Check compile-agentic version
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_COMPILED_VERSION: "v0.65.5"
+ 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/check_version_updates.cjs');
+ await main();
+ - name: Compute current body text
+ id: sanitized
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/compute_text.cjs');
+ await main();
+ - name: Add comment with workflow run link
+ id: add-comment
+ if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[Docs automation](https://github.com/elastic/docs-actions) | [From workflow: {workflow_name}]({run_url})\\n\\nReact with 👍 if helpful, 👎 if not.' }}\"}"
+ 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/add_workflow_run_comment.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_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }}
+ # poutine:ignore untrusted_checkout_exec
+ run: |
+ bash ${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh
+ {
+ cat << 'GH_AW_PROMPT_8c6ac726f069ce27_EOF'
+
+ GH_AW_PROMPT_8c6ac726f069ce27_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_8c6ac726f069ce27_EOF'
+
+ Tools: add_labels(max:30), missing_tool, missing_data, noop
+
+
+ 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_8c6ac726f069ce27_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then
+ cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md"
+ fi
+ cat << 'GH_AW_PROMPT_8c6ac726f069ce27_EOF'
+
+ {{#runtime-import .github/workflows/gh-aw-fragments/docs-tools.md}}
+ {{#runtime-import .github/workflows/gh-aw-fragments/formatting.md}}
+ {{#runtime-import .github/workflows/gh-aw-fragments/rigor.md}}
+ {{#runtime-import .github/workflows/gh-aw-fragments/mcp-pagination.md}}
+ {{#runtime-import .github/workflows/gh-aw-fragments/messages-footer.md}}
+ {{#runtime-import .github/workflows/gh-aw-issue-triage.md}}
+ GH_AW_PROMPT_8c6ac726f069ce27_EOF
+ } > "$GH_AW_PROMPT"
+ - name: Interpolate variables and render templates
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_EXPR_49B959F1: ${{ inputs.additional-instructions }}
+ 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/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_49B959F1: ${{ inputs.additional-instructions }}
+ GH_AW_GITHUB_ACTOR: ${{ github.actor }}
+ GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }}
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }}
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
+ GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
+ GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
+ GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
+ GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }}
+ 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+
+ 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_49B959F1: process.env.GH_AW_EXPR_49B959F1,
+ GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
+ GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID,
+ GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER,
+ GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER,
+ GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER,
+ GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
+ GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
+ GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
+ GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT,
+ 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
+ # 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: ${{ steps.artifact-prefix.outputs.prefix }}activation
+ path: |
+ /tmp/gh-aw/aw_info.json
+ /tmp/gh-aw/aw-prompts/prompt.txt
+ retention-days: 1
+
+ agent:
+ needs: activation
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ issues: read
+ env:
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
+ GH_AW_ASSETS_ALLOWED_EXTS: ""
+ GH_AW_ASSETS_BRANCH: ""
+ GH_AW_ASSETS_MAX_SIZE_KB: 0
+ GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs
+ GH_AW_WORKFLOW_ID_SANITIZED: ghawissuetriage
+ outputs:
+ artifact_prefix: ${{ needs.activation.outputs.artifact_prefix }}
+ checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
+ has_patch: ${{ steps.collect_output.outputs.has_patch }}
+ inference_access_error: ${{ steps.detect-inference-error.outputs.inference_access_error || 'false' }}
+ model: ${{ needs.activation.outputs.model }}
+ output: ${{ steps.collect_output.outputs.output }}
+ output_types: ${{ steps.collect_output.outputs.output_types }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Set runtime paths
+ id: set-runtime-paths
+ run: |
+ echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" >> "$GITHUB_OUTPUT"
+ echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" >> "$GITHUB_OUTPUT"
+ echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" >> "$GITHUB_OUTPUT"
+ - name: Checkout repository
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ - name: Create gh-aw temp directory
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh
+ - name: Configure gh CLI for GitHub Enterprise
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh
+ env:
+ GH_TOKEN: ${{ github.token }}
+ - env:
+ EVENT_NAME: ${{ github.event_name }}
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ REPO: ${{ github.repository }}
+ name: Fetch issue data
+ run: "mkdir -p /tmp/gh-aw/triage-data\n\nif [ \"$EVENT_NAME\" = \"workflow_dispatch\" ]; then\n # Batch mode: fetch all open needs-team issues\n gh issue list --repo \"$REPO\" \\\n --label \"needs-team\" \\\n --state open \\\n --json number,title,body,labels,author,createdAt,url \\\n --limit 25 \\\n > /tmp/gh-aw/triage-data/issues.json\nelse\n # Slash command mode: fetch the triggering issue\n gh issue view \"$ISSUE_NUMBER\" \\\n --repo \"$REPO\" \\\n --json number,title,body,labels,author,createdAt,url \\\n | jq '[.]' > /tmp/gh-aw/triage-data/issues.json\nfi\n\necho \"Issues to triage: $(jq 'length' /tmp/gh-aw/triage-data/issues.json)\"\n"
+ - env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ name: Fetch CODEOWNERS
+ run: "gh api \"repos/$REPO/contents/.github/CODEOWNERS\" \\\n --jq '.content' | base64 -d > /tmp/gh-aw/triage-data/CODEOWNERS || true\necho \"CODEOWNERS fetched.\"\n"
+ - 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]"
+ 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
+ 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs');
+ await main();
+ - name: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10
+ - 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('${{ runner.temp }}/gh-aw/actions/determine_automatic_lockdown.cjs');
+ await determineAutomaticLockdown(github, context, core);
+ - name: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10 ghcr.io/github/gh-aw-mcpg:v0.2.11 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine
+ - name: Write 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_0942fd3d186d7566_EOF'
+ {"add_labels":{"allowed":["Team:Admin","Team:Developer","Team:DocsEng","Team:Experience","Team:Ingest","Team:Projects","cross-team"],"max":30},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_0942fd3d186d7566_EOF
+ - name: Write Safe Outputs Tools
+ run: |
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/tools_meta.json << 'GH_AW_SAFE_OUTPUTS_TOOLS_META_ca37fe436e4ff605_EOF'
+ {
+ "description_suffixes": {
+ "add_labels": " CONSTRAINTS: Maximum 30 label(s) can be added. Only these labels are allowed: [\"Team:Admin\" \"Team:Developer\" \"Team:DocsEng\" \"Team:Experience\" \"Team:Ingest\" \"Team:Projects\" \"cross-team\"]."
+ },
+ "repo_params": {},
+ "dynamic_tools": []
+ }
+ GH_AW_SAFE_OUTPUTS_TOOLS_META_ca37fe436e4ff605_EOF
+ cat > ${RUNNER_TEMP}/gh-aw/safeoutputs/validation.json << 'GH_AW_SAFE_OUTPUTS_VALIDATION_997b146e31496d76_EOF'
+ {
+ "add_labels": {
+ "defaultMax": 5,
+ "fields": {
+ "item_number": {
+ "issueNumberOrTemporaryId": true
+ },
+ "labels": {
+ "required": true,
+ "type": "array",
+ "itemType": "string",
+ "itemSanitize": true,
+ "itemMaxLength": 128
+ },
+ "repo": {
+ "type": "string",
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_data": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "context": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "data_type": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ },
+ "reason": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ }
+ }
+ },
+ "missing_tool": {
+ "defaultMax": 20,
+ "fields": {
+ "alternatives": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 512
+ },
+ "reason": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 256
+ },
+ "tool": {
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 128
+ }
+ }
+ },
+ "noop": {
+ "defaultMax": 1,
+ "fields": {
+ "message": {
+ "required": true,
+ "type": "string",
+ "sanitize": true,
+ "maxLength": 65000
+ }
+ }
+ }
+ }
+ GH_AW_SAFE_OUTPUTS_VALIDATION_997b146e31496d76_EOF
+ node ${RUNNER_TEMP}/gh-aw/actions/generate_safe_outputs_tools.cjs
+ - 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: ${{ 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_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 /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 MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
+ 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 MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -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.2.11'
+
+ mkdir -p /home/runner/.copilot
+ cat << GH_AW_MCP_CONFIG_93b8beb9727079bb_EOF | bash ${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh
+ {
+ "mcpServers": {
+ "elastic-docs": {
+ "type": "http",
+ "url": "https://www.elastic.co/docs/_mcp/",
+ "tools": [
+ "SemanticSearch",
+ "GetDocumentByUrl",
+ "FindRelatedDocs",
+ "FindInconsistencies"
+ ],
+ "guard-policies": {
+ "write-sink": {
+ "accept": [
+ "*"
+ ]
+ }
+ }
+ },
+ "github": {
+ "type": "stdio",
+ "container": "ghcr.io/github/github-mcp-server:v0.32.0",
+ "env": {
+ "GITHUB_HOST": "\${GITHUB_SERVER_URL}",
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}",
+ "GITHUB_READ_ONLY": "1",
+ "GITHUB_TOOLSETS": "issues,labels,repos"
+ },
+ "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_93b8beb9727079bb_EOF
+ - name: Download activation artifact
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}activation
+ path: /tmp/gh-aw
+ - name: Clean git credentials
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh
+ - name: Execute GitHub Copilot CLI
+ id: agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ # --allow-tool elastic-docs
+ # --allow-tool elastic-docs(FindInconsistencies)
+ # --allow-tool elastic-docs(FindRelatedDocs)
+ # --allow-tool elastic-docs(GetDocumentByUrl)
+ # --allow-tool elastic-docs(SemanticSearch)
+ # --allow-tool github
+ # --allow-tool safeoutputs
+ # --allow-tool shell(cat *)
+ # --allow-tool shell(cat)
+ # --allow-tool shell(date)
+ # --allow-tool shell(echo)
+ # --allow-tool shell(gh api *)
+ # --allow-tool shell(grep)
+ # --allow-tool shell(head)
+ # --allow-tool shell(jq *)
+ # --allow-tool shell(ls)
+ # --allow-tool shell(pwd)
+ # --allow-tool shell(sort)
+ # --allow-tool shell(tail)
+ # --allow-tool shell(uniq)
+ # --allow-tool shell(wc)
+ # --allow-tool shell(yq)
+ # --allow-tool write
+ timeout-minutes: 10
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --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" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-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,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.elastic.co,www.googleapis.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --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/ --disable-builtin-mcps --allow-tool elastic-docs --allow-tool '\''elastic-docs(FindInconsistencies)'\'' --allow-tool '\''elastic-docs(FindRelatedDocs)'\'' --allow-tool '\''elastic-docs(GetDocumentByUrl)'\'' --allow-tool '\''elastic-docs(SemanticSearch)'\'' --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat *)'\'' --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(gh api *)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(jq *)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || '' }}
+ GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json
+ 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_VERSION: v0.65.5
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ 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]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Detect inference access error
+ id: detect-inference-error
+ if: always()
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/detect_inference_access_error.sh
+ - name: Configure Git credentials
+ env:
+ REPO_NAME: ${{ github.repository }}
+ SERVER_URL: ${{ github.server_url }}
+ run: |
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
+ git config --global user.name "github-actions[bot]"
+ git config --global am.keepcr true
+ # Re-authenticate git with GitHub token
+ SERVER_URL_STRIPPED="${SERVER_URL#https://}"
+ git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git"
+ echo "Git configured with standard GitHub Actions identity"
+ - name: 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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/redact_secrets.cjs');
+ await main();
+ env:
+ GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
+ SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
+ SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
+ SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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,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.elastic.co,www.googleapis.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ GH_AW_COMMAND: triage
+ 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/collect_ndjson_output.cjs');
+ await main();
+ - 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ 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 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: Parse token usage for step summary
+ if: always()
+ continue-on-error: true
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/parse_token_usage.sh
+ - 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@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}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-stdio.log
+ /tmp/gh-aw/agent/
+ /tmp/gh-aw/safeoutputs.jsonl
+ /tmp/gh-aw/agent_output.json
+ /tmp/gh-aw/aw-*.patch
+ /tmp/gh-aw/aw-*.bundle
+ if-no-files-found: ignore
+ - name: Upload firewall audit logs
+ if: always()
+ continue-on-error: true
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}firewall-audit-logs
+ path: |
+ /tmp/gh-aw/sandbox/firewall/logs/
+ /tmp/gh-aw/sandbox/firewall/audit/
+ if-no-files-found: ignore
+
+ conclusion:
+ needs:
+ - activation
+ - agent
+ - detection
+ - safe_outputs
+ if: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true')
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ concurrency:
+ group: "gh-aw-conclusion-gh-aw-issue-triage"
+ cancel-in-progress: false
+ 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@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}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: Process No-Op Messages
+ id: noop
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_NOOP_MAX: "1"
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ 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);
+ const { main } = require('${{ runner.temp }}/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: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs');
+ await main();
+ - name: Handle Agent Failure
+ id: handle_agent_failure
+ if: always()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ 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-issue-triage"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }}
+ GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
+ GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
+ GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[Docs automation](https://github.com/elastic/docs-actions) | [From workflow: {workflow_name}]({run_url})\\n\\nReact with 👍 if helpful, 👎 if not.' }}\"}"
+ GH_AW_GROUP_REPORTS: "false"
+ GH_AW_FAILURE_REPORT_AS_ISSUE: "true"
+ GH_AW_TIMEOUT_MINUTES: "10"
+ 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);
+ const { main } = require('${{ runner.temp }}/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: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ 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('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs');
+ await main();
+ - name: Update reaction comment with completion status
+ id: conclusion
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ 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_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }}
+ GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
+ GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[Docs automation](https://github.com/elastic/docs-actions) | [From workflow: {workflow_name}]({run_url})\\n\\nReact with 👍 if helpful, 👎 if not.' }}\"}"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/notify_comment_error.cjs');
+ await main();
+
+ detection:
+ needs: 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
+ outputs:
+ detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
+ detection_success: ${{ steps.detection_conclusion.outputs.success }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.agent.outputs.artifact_prefix }}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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: false
+ # --- Threat Detection ---
+ - name: Download container images
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh ghcr.io/github/gh-aw-firewall/agent:0.25.10 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.10 ghcr.io/github/gh-aw-firewall/squid:0.25.10
+ - 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 configuration for detection
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ rm -f /tmp/gh-aw/mcp-config/mcp-servers.json
+ rm -f /home/runner/.copilot/mcp-config.json
+ rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
+ - name: Prepare threat detection files
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ run: |
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ done
+ 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ WORKFLOW_NAME: "Issue Triage Agent"
+ WORKFLOW_DESCRIPTION: "Triages issues by analyzing content and applying the appropriate team label. Triggered by typing /triage on an issue comment, or manually via workflow_dispatch."
+ 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);
+ 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: Install GitHub Copilot CLI
+ run: ${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh latest
+ - name: Install AWF binary
+ run: bash ${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh v0.25.10
+ - name: Execute GitHub Copilot CLI
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ id: detection_agentic_execution
+ # Copilot CLI tool arguments (sorted):
+ timeout-minutes: 20
+ run: |
+ set -o pipefail
+ touch /tmp/gh-aw/agent-step-summary.md
+ # shellcheck disable=SC1003
+ sudo -E awf --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" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.10 --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/ --disable-builtin-mcps --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ env:
+ COPILOT_AGENT_RUNNER_TYPE: STANDALONE
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
+ COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || '' }}
+ GH_AW_PHASE: detection
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ GH_AW_VERSION: v0.65.5
+ GITHUB_API_URL: ${{ github.api_url }}
+ GITHUB_AW: true
+ 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]
+ XDG_CONFIG_HOME: /home/runner
+ - name: Upload threat detection log
+ if: always() && steps.detection_guard.outputs.run_detection == 'true'
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: ${{ needs.agent.outputs.artifact_prefix }}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()
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
+ 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/parse_threat_detection_results.cjs');
+ await main();
+
+ pre_activation:
+ if: "(github.event_name == 'issue_comment' || github.event_name == 'issues') && (github.event_name == 'issues' && (startsWith(github.event.issue.body, '/triage ') || startsWith(github.event.issue.body, '/triage\n') || github.event.issue.body == '/triage') || github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/triage ') || startsWith(github.event.comment.body, '/triage\n') || github.event.comment.body == '/triage') && github.event.issue.pull_request == null) || (!(github.event_name == 'issue_comment')) && (!(github.event_name == 'issues'))"
+ runs-on: ubuntu-slim
+ outputs:
+ activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }}
+ matched_command: ${{ steps.check_command_position.outputs.matched_command }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Check team membership for command workflow
+ id: check_membership
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_REQUIRED_ROLES: "admin,maintainer,write"
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
+ setupGlobals(core, github, context, exec, io);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs');
+ await main();
+ - name: Check command position
+ id: check_command_position
+ uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_COMMANDS: "[\"triage\"]"
+ 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/check_command_position.cjs');
+ await main();
+
+ safe_outputs:
+ needs:
+ - activation
+ - agent
+ - detection
+ if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
+ runs-on: ubuntu-slim
+ permissions:
+ contents: read
+ issues: write
+ pull-requests: write
+ timeout-minutes: 15
+ env:
+ GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/gh-aw-issue-triage"
+ GH_AW_ENGINE_ID: "copilot"
+ GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }}
+ GH_AW_SAFE_OUTPUT_MESSAGES: "{\"footer\":\"${{ inputs.messages-footer || '---\\n[Docs automation](https://github.com/elastic/docs-actions) | [From workflow: {workflow_name}]({run_url})\\n\\nReact with 👍 if helpful, 👎 if not.' }}\"}"
+ GH_AW_WORKFLOW_ID: "gh-aw-issue-triage"
+ GH_AW_WORKFLOW_NAME: "Issue Triage Agent"
+ outputs:
+ code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
+ code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
+ create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
+ create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
+ process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
+ process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
+ steps:
+ - name: Setup Scripts
+ uses: github/gh-aw-actions/setup@15b2fa31e9a1b771c9773c162273924d8f5ea516 # v0.65.5
+ with:
+ destination: ${{ runner.temp }}/gh-aw/actions
+ - name: Download agent output artifact
+ id: download-agent-output
+ continue-on-error: true
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}agent
+ path: /tmp/gh-aw/
+ - name: Setup agent output environment variable
+ id: setup-agent-output-env
+ if: steps.download-agent-output.outcome == 'success'
+ run: |
+ mkdir -p /tmp/gh-aw/
+ find "/tmp/gh-aw/" -type f -print
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
+ - name: Configure GH_HOST for enterprise compatibility
+ id: ghes-host-config
+ shell: bash
+ run: |
+ # 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@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
+ env:
+ GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
+ 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,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.elastic.co,www.googleapis.com"
+ GITHUB_SERVER_URL: ${{ github.server_url }}
+ GITHUB_API_URL: ${{ github.api_url }}
+ GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"allowed\":[\"Team:Admin\",\"Team:Developer\",\"Team:DocsEng\",\"Team:Experience\",\"Team:Ingest\",\"Team:Projects\",\"cross-team\"],\"max\":30},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"}}"
+ 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);
+ const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs');
+ await main();
+ - name: Upload Safe Output Items
+ if: always()
+ uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
+ with:
+ name: ${{ needs.activation.outputs.artifact_prefix }}safe-output-items
+ path: /tmp/gh-aw/safe-output-items.jsonl
+ if-no-files-found: ignore
+
diff --git a/README.md b/README.md
index b27c728..9635e28 100644
--- a/README.md
+++ b/README.md
@@ -28,6 +28,7 @@ AI-powered [GitHub Agent Workflows](https://github.github.com/gh-aw/) for docume
| Workflow | Description | Trigger | Safe Output |
|----------|-------------|---------|-------------|
| [docs-check](agentic-workflows/docs-check/) | Analyze a PR or commit for documentation impact | `/docs-check`, label, dispatch | `add-comment` |
+| [issue-triage](agentic-workflows/issue-triage/) | Triage issues by applying team labels | `/triage`, dispatch | `add-labels` |
Quick install:
diff --git a/agentic-workflows/README.md b/agentic-workflows/README.md
index 96195dd..f7ecf8e 100644
--- a/agentic-workflows/README.md
+++ b/agentic-workflows/README.md
@@ -5,6 +5,7 @@ AI-powered workflows for Elastic documentation tasks. Each directory contains a
| Workflow | Description | Trigger | Safe Output |
|----------|-------------|---------|-------------|
| [docs-check](docs-check/) | Analyze a PR or commit for documentation impact | `/docs-check`, label, dispatch | `add-comment` |
+| [issue-triage](issue-triage/) | Triage issues by applying team labels | `/triage`, dispatch | `add-labels` |
## Installation
diff --git a/agentic-workflows/fragments/formatting.md b/agentic-workflows/fragments/formatting.md
new file mode 100644
index 0000000..a5a8a15
--- /dev/null
+++ b/agentic-workflows/fragments/formatting.md
@@ -0,0 +1,7 @@
+## 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
diff --git a/agentic-workflows/fragments/mcp-pagination.md b/agentic-workflows/fragments/mcp-pagination.md
new file mode 100644
index 0000000..44054a3
--- /dev/null
+++ b/agentic-workflows/fragments/mcp-pagination.md
@@ -0,0 +1,32 @@
+## 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
diff --git a/agentic-workflows/fragments/rigor.md b/agentic-workflows/fragments/rigor.md
new file mode 100644
index 0000000..9b72217
--- /dev/null
+++ b/agentic-workflows/fragments/rigor.md
@@ -0,0 +1,10 @@
+## 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.
diff --git a/agentic-workflows/fragments/runtime-setup.md b/agentic-workflows/fragments/runtime-setup.md
new file mode 100644
index 0000000..67f905f
--- /dev/null
+++ b/agentic-workflows/fragments/runtime-setup.md
@@ -0,0 +1,53 @@
+---
+steps:
+ - name: Setup Go
+ if: hashFiles('go.mod') != ''
+ uses: actions/setup-go@v5
+ with:
+ go-version-file: go.mod
+ cache: true
+
+ - name: Setup Python
+ if: hashFiles('.python-version') != ''
+ uses: actions/setup-python@v5
+ with:
+ python-version-file: '.python-version'
+
+ - name: Setup Node.js (.node-version)
+ if: hashFiles('.node-version') != ''
+ uses: actions/setup-node@v6
+ with:
+ node-version-file: '.node-version'
+
+ - name: Setup Node.js (.nvmrc)
+ if: hashFiles('.node-version') == '' && hashFiles('.nvmrc') != ''
+ uses: actions/setup-node@v6
+ with:
+ node-version-file: '.nvmrc'
+
+ - name: Setup Ruby
+ if: hashFiles('.ruby-version') != ''
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: '.ruby-version'
+ bundler-cache: true
+
+ - name: Setup uv
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ uses: astral-sh/setup-uv@v5
+ id: setup-uv
+
+ - name: Expose uv in workspace
+ if: hashFiles('pyproject.toml', 'uv.lock') != ''
+ shell: bash
+ env:
+ UV_PATH: ${{ steps.setup-uv.outputs.uv-path }}
+ WORKSPACE: ${{ github.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"
+---
diff --git a/agentic-workflows/fragments/scheduled-report.md b/agentic-workflows/fragments/scheduled-report.md
new file mode 100644
index 0000000..b61c57a
--- /dev/null
+++ b/agentic-workflows/fragments/scheduled-report.md
@@ -0,0 +1,67 @@
+# Scheduled Report Agent
+
+You are a report agent for ${{ github.repository }}. You run on a schedule to investigate the repository and file an issue when something needs attention. Your specific assignment is described in the **Report Assignment** section below.
+
+## Context
+
+- **Repository**: ${{ github.repository }}
+
+## Constraints
+
+This workflow is for detection and reporting only. You can read files, search code, run commands, and read PR/issue details — but your only output is either a single issue or a noop.
+
+## Process
+
+Follow these steps in order.
+
+### 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. Follow the data gathering instructions in the **Report Assignment** section.
+
+### Step 2: Analyze
+
+Follow the analysis instructions in the **Report Assignment** section to determine whether an issue should be filed. The Report Assignment defines:
+- What data to gather and how
+- What to look for
+- What constitutes a finding worth reporting
+- What to skip or ignore
+
+### Step 3: Self-Review (Quality Gate)
+
+Before filing anything, critically evaluate every finding against these criteria:
+
+1. **Evidence is concrete** — you can point to exact file paths, line numbers, commit SHAs, or command outputs. No "I believe" or "it seems."
+2. **Finding is actionable** — a maintainer reading the issue can act on it without re-investigating from scratch.
+3. **Finding is not already tracked** — you checked open issues and recent PRs for duplicates.
+4. **Finding is worth a human's time** — the issue is material enough that a maintainer would thank you for filing it, not close it as noise.
+
+If zero findings pass all four criteria, call `noop` with a brief reason and stop. **Noop is the expected outcome most days.** Filing nothing is a success when there is nothing worth filing.
+
+### Step 4: Report
+
+If there are findings that pass the quality gate, call `create_issue` with a structured report. Use the issue format specified in the Report Assignment if one is provided, otherwise use this default format:
+
+**Issue title:** Brief summary of findings
+
+**Issue body:**
+
+> ## Findings
+>
+> ### 1. [Brief description]
+>
+> **Evidence:** [Links, references, or data supporting the finding]
+> **Action needed:** [What should be done]
+>
+> ## Suggested Actions
+>
+> - [ ] [Actionable checkbox for each finding]
+
+**Guidelines:**
+- Group related findings together
+- Be specific about what needs to happen
+- Include links and references where possible
+- Make suggested actions concrete enough to act on without re-investigating
+- If a finding is ambiguous, it does not pass the quality gate — drop it
+
+## Report Assignment
diff --git a/agentic-workflows/issue-triage/README.md b/agentic-workflows/issue-triage/README.md
new file mode 100644
index 0000000..694acad
--- /dev/null
+++ b/agentic-workflows/issue-triage/README.md
@@ -0,0 +1,48 @@
+# Issue Triage
+
+Triages issues by analyzing content and applying the appropriate team label. Uses the Elastic Docs MCP server and CODEOWNERS to determine ownership.
+
+## Triggers
+
+| Event | Description |
+|-------|-------------|
+| `/triage` | Slash command on an issue comment |
+| `workflow_dispatch` | Manual trigger (batch mode: triages all `needs-team` issues) |
+
+## Install
+
+```bash
+mkdir -p .github/workflows && curl -sL \
+ https://raw.githubusercontent.com/elastic/docs-actions/v1/agentic-workflows/issue-triage/example.yml \
+ -o .github/workflows/issue-triage.yml
+```
+
+Ensure the `COPILOT_GITHUB_TOKEN` secret is configured in your repository.
+
+## Inputs
+
+| Input | Type | Required | Default | Description |
+|-------|------|----------|---------|-------------|
+| `additional-instructions` | string | No | `""` | Team mapping, label rules, CODEOWNERS paths |
+| `setup-commands` | string | No | `""` | Shell commands to run before the agent starts |
+| `messages-footer` | string | No | _(default footer)_ | Footer appended to all agent comments |
+
+## Safe Outputs
+
+| Output | Max | Description |
+|--------|-----|-------------|
+| `noop` | — | No issues to triage or no labels needed |
+| `add-labels` | 30 | Apply team labels to issues |
+
+Allowed labels: `Team:Admin`, `Team:Developer`, `Team:DocsEng`, `Team:Experience`, `Team:Ingest`, `Team:Projects`, `cross-team`.
+
+## How it works
+
+1. Pre-steps fetch issue data and CODEOWNERS to `/tmp/gh-aw/triage-data/`
+2. The agent reads each issue, uses the Elastic Docs MCP server to gather context about referenced pages
+3. For docs stored outside the current repo, it fetches the relevant repo's CODEOWNERS via `gh api`
+4. It applies the best-fit team label via `add_labels`
+
+## Example
+
+The `example.yml` includes the full docs-content team mapping via `additional-instructions`. Customize the mapping for your repo's team structure.
diff --git a/agentic-workflows/issue-triage/example.yml b/agentic-workflows/issue-triage/example.yml
new file mode 100644
index 0000000..13aa6b6
--- /dev/null
+++ b/agentic-workflows/issue-triage/example.yml
@@ -0,0 +1,33 @@
+name: Issue Triage
+on:
+ issue_comment:
+ types: [created]
+ issues:
+ types: [opened, labeled]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ issues: read
+
+jobs:
+ run:
+ uses: elastic/docs-actions/.github/workflows/gh-aw-issue-triage.lock.yml@v1
+ with:
+ additional-instructions: |
+ ## Team Ownership Mapping
+
+ | Team Label | Owns | Keywords / URL paths |
+ |---|---|---|
+ | `Team:Admin` | Elasticsearch admin, cluster mgmt, cloud, deployment docs | Cluster management, index management, security (auth, roles, API keys), snapshots, ILM, data lifecycle, CCR, CCS, licensing, upgrade, monitoring, Elastic Cloud, ECE, ECK, serverless. Paths: `deploy-manage/`, `cloud-account/`, `manage-data/` (except `manage-data/ingest/`), `serverless/`, `troubleshoot/deployments/`, `troubleshoot/elasticsearch/` |
+ | `Team:Experience` | Kibana, observability solution, security solution | Kibana, dashboards, visualizations, Discover, observability, APM UI, maps, canvas, Lens, alerting, rules, cases, SIEM, endpoint security, detection rules, security analytics. Paths: `explore-analyze/` (default), `solutions/observability/`, `solutions/security/`, `reference/kibana/`, `reference/observability/`, `reference/security/` |
+ | `Team:Developer` | Elasticsearch developer, search solution docs | Elasticsearch APIs, ES|QL, query DSL, search features, relevance, vector search, semantic search, inference APIs, connectors, developer tools, clients, search applications, App Search, Workplace Search. Paths: `solutions/search/`, `reference/elasticsearch/clients/`, `reference/search/`, `reference/machine-learning/`, `explore-analyze/ai-features/`, `explore-analyze/cross-cluster-search/`, `explore-analyze/cross-project-search/`, `explore-analyze/transforms/` |
+ | `Team:Ingest` | Data ingestion, Fleet, APM agents docs | Fleet, Elastic Agent, Beats, Logstash, pipelines, integrations, data streams, APM agents, APM server, OpenTelemetry. Paths: `manage-data/ingest/`, `reference/apm-agents/`, `reference/fleet/`, `reference/ingestion-tools/`, `solutions/observability/apm/apm-agents/`, `solutions/observability/apm/apm-server/`, `solutions/observability/apm/ingest/`, `solutions/observability/apm/opentelemetry/` |
+ | `Team:DocsEng` | Docs infrastructure | Docs build system, CI/CD, website rendering, broken navigation, docs-builder bugs, elastic.co website issues not related to content. Paths: `.github/workflows/`, `.github/scripts/` |
+ | `Team:Projects` | Internal projects, docs initiatives, fallback | Internal documentation projects, content strategy, information architecture, get-started guides, or when no other team fits. Paths: `get-started/` |
+
+ **Shared ownership**: Some paths have shared ownership (e.g., `/explore-analyze/machine-learning/` is shared by Developer and Experience). Pick the team whose keywords best match the issue content.
+ **Internal projects**: If the issue seems related to an internal documentation project, initiative, or content strategy effort, apply `Team:Projects`.
+ **Fallback**: Use `Team:Projects` if you genuinely cannot determine the owning team.
+ secrets:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
diff --git a/agentic-workflows/issue-triage/gh-aw-issue-triage.md b/agentic-workflows/issue-triage/gh-aw-issue-triage.md
new file mode 100644
index 0000000..5cd3378
--- /dev/null
+++ b/agentic-workflows/issue-triage/gh-aw-issue-triage.md
@@ -0,0 +1,179 @@
+---
+description: >
+ Triages issues by analyzing content and applying the appropriate team label.
+ Triggered by typing /triage on an issue comment, or manually via workflow_dispatch.
+
+imports:
+ - gh-aw-fragments/docs-tools.md
+ - gh-aw-fragments/formatting.md
+ - gh-aw-fragments/rigor.md
+ - gh-aw-fragments/mcp-pagination.md
+ - gh-aw-fragments/messages-footer.md
+
+engine:
+ id: copilot
+
+on:
+ roles: [admin, maintainer, write]
+ slash_command:
+ name: triage
+ events: [issue_comment, issues]
+ workflow_dispatch:
+ workflow_call:
+ inputs:
+ additional-instructions:
+ description: "Repo-specific instructions — team mapping, label rules, CODEOWNERS paths"
+ type: string
+ required: false
+ default: ""
+ setup-commands:
+ description: "Shell commands to run before the agent starts"
+ type: string
+ required: false
+ default: ""
+ messages-footer:
+ description: "Footer appended to all agent comments"
+ type: string
+ required: false
+ default: ""
+ secrets:
+ COPILOT_GITHUB_TOKEN:
+ required: true
+
+concurrency:
+ group: issue-triage
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ issues: read
+
+strict: true
+
+tools:
+ github:
+ toolsets: [issues, labels, repos]
+ bash:
+ - "cat *"
+ - "jq *"
+ - "gh api *"
+
+mcp-servers:
+ elastic-docs:
+ url: "https://www.elastic.co/docs/_mcp/"
+ allowed: ["*"]
+
+network:
+ allowed:
+ - defaults
+ - github
+ - "www.elastic.co"
+
+steps:
+ - name: Fetch issue data
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ EVENT_NAME: ${{ github.event_name }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ REPO: ${{ github.repository }}
+ run: |
+ mkdir -p /tmp/gh-aw/triage-data
+
+ if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
+ # Batch mode: fetch all open needs-team issues
+ gh issue list --repo "$REPO" \
+ --label "needs-team" \
+ --state open \
+ --json number,title,body,labels,author,createdAt,url \
+ --limit 25 \
+ > /tmp/gh-aw/triage-data/issues.json
+ else
+ # Slash command mode: fetch the triggering issue
+ gh issue view "$ISSUE_NUMBER" \
+ --repo "$REPO" \
+ --json number,title,body,labels,author,createdAt,url \
+ | jq '[.]' > /tmp/gh-aw/triage-data/issues.json
+ fi
+
+ echo "Issues to triage: $(jq 'length' /tmp/gh-aw/triage-data/issues.json)"
+
+ - name: Fetch CODEOWNERS
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ run: |
+ gh api "repos/$REPO/contents/.github/CODEOWNERS" \
+ --jq '.content' | base64 -d > /tmp/gh-aw/triage-data/CODEOWNERS || true
+ echo "CODEOWNERS fetched."
+
+ - name: Repo-specific setup
+ if: ${{ inputs.setup-commands != '' }}
+ env:
+ SETUP_COMMANDS: ${{ inputs.setup-commands }}
+ run: eval "$SETUP_COMMANDS"
+
+safe-outputs:
+ noop:
+ add-labels:
+ allowed:
+ - "Team:Admin"
+ - "Team:Developer"
+ - "Team:DocsEng"
+ - "Team:Experience"
+ - "Team:Ingest"
+ - "Team:Projects"
+ - "cross-team"
+ max: 30
+
+timeout-minutes: 10
+---
+
+# Issue Triage Agent
+
+You are a triage agent for ${{ github.repository }}. Your job is to read issues and apply the correct team label based on the issue content.
+
+## Pre-Downloaded Data
+
+- **Issues**: `/tmp/gh-aw/triage-data/issues.json` — the issue(s) to triage.
+- **CODEOWNERS**: `/tmp/gh-aw/triage-data/CODEOWNERS` — maps file paths to owning teams.
+
+Use `cat` and `jq` to read these files.
+
+If the issues JSON contains an empty array (`[]`), call `noop` with message "No issues to triage" and stop.
+
+## Available Tools
+
+You have access to the **Elastic Docs MCP server** (`elastic-docs`). Use it to:
+- **`search_docs`**: Search published Elastic documentation by keyword or topic. Use this to understand what area of the product an issue relates to.
+- **`get_document_by_url`**: Retrieve a specific docs page by URL. Use this when an issue links to a specific page — fetch it to understand its content and which product area it belongs to.
+- **`find_related_docs`**: Find docs related to a topic. Use this when the issue is vague and you need more context.
+
+You also have access to **`gh api`** via bash. Use it to fetch CODEOWNERS from other repos when an issue references docs stored outside the current repository.
+
+## Rules
+
+1. **One primary team label per issue.** Pick the best fit.
+2. **Add `cross-team`** alongside the primary label only if the issue clearly spans multiple teams.
+3. **URL paths are the strongest signal.** If the issue references a specific `elastic.co/docs/` or `elastic.co/guide/` URL, extract the path and match against CODEOWNERS and the team mapping. Use `get_document_by_url` from the Elastic Docs MCP server to fetch the page and confirm which product area it covers.
+4. **Use the Elastic Docs MCP server** to gather context. If the issue mentions a topic but no URL, use `search_docs` to find the relevant docs and determine which team owns that area.
+5. **When ambiguous, prefer the team that owns the most relevant page.** Do not guess — use the tools available to you to gather evidence.
+6. **Docs in other repos**: If the issue references documentation stored outside the current repository, use `gh api repos/{owner}/{repo}/contents/.github/CODEOWNERS` to fetch that repo's CODEOWNERS and identify the owning team. Map the result back to the team labels if possible.
+7. **Fallback to the default team** only if you genuinely cannot determine the owning team.
+8. **Do NOT add comments.** Only apply labels.
+
+## Process
+
+For each issue in the JSON:
+
+1. Read the title, body, and existing labels
+2. If the issue already has a `Team:*` label, skip it
+3. Look for URLs pointing to specific docs pages — extract the path and match against the CODEOWNERS file and the team mapping
+4. Use the Elastic Docs MCP server to fetch referenced pages or search for related docs to confirm the product area
+5. If the issue references docs in another repo, fetch that repo's CODEOWNERS via `gh api` to identify the owning team
+6. Look for product/feature keywords in the title and body
+7. Determine the best-fit team label
+8. Apply the label using `add_labels`
+
+When done with all issues, if no labels were needed, call `noop` with a brief explanation.
+
+${{ inputs.additional-instructions }}