mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
b157c39779
Preserve the reviewed transactional publication and provenance contracts while incorporating the concurrent branch history. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2764 lines
159 KiB
YAML
Generated
2764 lines
159 KiB
YAML
Generated
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"826a6b2841cd392e979539cf253eb54bb47dc7a0f9bb79b0764d6f6a5988768e","body_hash":"135ed81604fcec85baf81604bec09961e23447a4a877e5891e9af24ca2cff2fd","compiler_version":"v0.88.7","strict":true,"agent_id":"copilot","agent_model":"${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'gpt-5.6-sol' }}","engine_versions":{"copilot":"1.0.80"}}
|
||
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GH_AW_DEFAULT_OTLP_HEADERS","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"3d3c42e5aac5ba805825da76410c181273ba90b1","version":"v7.0.1"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"820762786026740c76f36085b0efc47a31fe5020","version":"v7.0.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5e508589e03a7757a7e05b26e834292f5445bfb6","version":"v0.88.7"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.14","digest":"sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14","digest":"sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.14","digest":"sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.18","digest":"sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:33e1ec1d967ac1f28c2cedc24ce103dea3226840626de345d3fe579e96cf5c7d","pinned_image":"ghcr.io/github/gh-aw-node@sha256:33e1ec1d967ac1f28c2cedc24ce103dea3226840626de345d3fe579e96cf5c7d"},{"image":"ghcr.io/github/github-mcp-server:v1.11.0","digest":"sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699","pinned_image":"ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699"}],"mcp_servers":[{"name":"github","tools":["actions_get","actions_list","get_commit","get_file_contents","get_job_logs","get_latest_release","get_release_by_tag","get_tag","issue_read","list_branches","list_commits","list_issue_types","list_issues","list_releases","list_starred_repositories","list_tags","search_code","search_issues","search_repositories"]},{"name":"safeoutputs","tools":["missing_data","missing_tool","noop","publish_health_report"]}]}
|
||
# This file was automatically generated by gh-aw (v0.88.7). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
|
||
#
|
||
# ___ _ _
|
||
# / _ \ | | (_)
|
||
# | |_| | __ _ ___ _ __ | |_ _ ___
|
||
# | _ |/ _` |/ _ \ '_ \| __| |/ __|
|
||
# | | | | (_| | __/ | | | |_| | (__
|
||
# \_| |_/\__, |\___|_| |_|\__|_|\___|
|
||
# __/ |
|
||
# _ _ |___/
|
||
# | | | | / _| |
|
||
# | | | | ___ _ __ _ __| |_| | _____ ____
|
||
# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___|
|
||
# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \
|
||
# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/
|
||
#
|
||
#
|
||
# To update this file, edit the corresponding .md file and run:
|
||
# gh aw compile
|
||
# Not all edits will cause changes to this file.
|
||
#
|
||
# For more information: https://github.github.com/gh-aw/introduction/overview/
|
||
#
|
||
# Orchestrator workflow that collects repo infrastructure health signals daily (pipelines, CI/CD infrastructure, resource usage), computes a fingerprint-based diff against the previous run, updates a pinned health dashboard issue, and dispatches investigation workers for new critical/warning findings. Focused on pipeline, infrastructure, and resource usage health only — does not track individual skill quality or PR review status.
|
||
#
|
||
# Resolved workflow manifest:
|
||
# Imports:
|
||
# - shared/pat_pool.md
|
||
# - ../aw/shared/devops-health.lock.md
|
||
#
|
||
# Secrets used:
|
||
# - COPILOT_PAT_0
|
||
# - COPILOT_PAT_1
|
||
# - COPILOT_PAT_2
|
||
# - COPILOT_PAT_3
|
||
# - COPILOT_PAT_4
|
||
# - COPILOT_PAT_5
|
||
# - COPILOT_PAT_6
|
||
# - COPILOT_PAT_7
|
||
# - COPILOT_PAT_8
|
||
# - COPILOT_PAT_9
|
||
# - GH_AW_DEFAULT_OTLP_HEADERS
|
||
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
|
||
# - GH_AW_GITHUB_TOKEN
|
||
# - GITHUB_TOKEN
|
||
#
|
||
# Custom actions used:
|
||
# - actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
# - actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
# - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
|
||
# - actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||
# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
# - github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
#
|
||
# Container images used:
|
||
# - ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98
|
||
# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5
|
||
# - ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5
|
||
# - ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53
|
||
# - ghcr.io/github/gh-aw-node@sha256:33e1ec1d967ac1f28c2cedc24ce103dea3226840626de345d3fe579e96cf5c7d
|
||
# - ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
|
||
|
||
name: "DevOps Daily Health Check"
|
||
on:
|
||
# permissions: {} # Permissions applied to pre-activation job
|
||
schedule:
|
||
- cron: "0 3 * * *"
|
||
workflow_dispatch:
|
||
inputs:
|
||
aw_context:
|
||
default: ""
|
||
description: "Agent caller context (used internally by Agentic Workflows)."
|
||
required: false
|
||
type: string
|
||
|
||
permissions: {}
|
||
|
||
concurrency:
|
||
cancel-in-progress: false
|
||
group: gh-aw-devops-health-dashboard
|
||
queue: max
|
||
|
||
run-name: "DevOps Daily Health Check"
|
||
|
||
env:
|
||
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}
|
||
OTEL_SERVICE_NAME: gh-aw.devops-health-check
|
||
OTEL_RESOURCE_ATTRIBUTES: 'gh-aw.workflow.name=DevOps%20Daily%20Health%20Check,gh-aw.repository=${{ github.repository }},gh-aw.run.id=${{ github.run_id }},github.run_id=${{ github.run_id }},gh-aw.engine.id=copilot'
|
||
OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}
|
||
GH_AW_OTLP_ENDPOINTS: '[{"url":"${{ vars.GH_AW_DEFAULT_OTLP_ENDPOINT }}","headers":"${{ secrets.GH_AW_DEFAULT_OTLP_HEADERS }}"}]'
|
||
GH_AW_OTLP_IF_MISSING: ignore
|
||
|
||
jobs:
|
||
activation:
|
||
needs:
|
||
- pat_pool
|
||
- pre_activation
|
||
if: >
|
||
needs.pre_activation.outputs.activated == 'true' && ((!(github.event_name == 'schedule' && github.event.repository.fork)))
|
||
runs-on: ubuntu-slim
|
||
permissions:
|
||
actions: read
|
||
contents: read
|
||
env:
|
||
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
outputs:
|
||
comment_id: ""
|
||
comment_repo: ""
|
||
daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }}
|
||
daily_ai_credits_guardrail_status: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_guardrail_status || '' }}
|
||
daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }}
|
||
daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }}
|
||
engine_id: ${{ steps.generate_aw_info.outputs.engine_id }}
|
||
lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }}
|
||
model: ${{ steps.generate_aw_info.outputs.model }}
|
||
oauth_token_check_failed: ${{ steps.check-oauth-tokens.outputs.oauth_token_check_failed == 'true' }}
|
||
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
|
||
setup-span-id: ${{ steps.setup.outputs.span-id }}
|
||
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
|
||
stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }}
|
||
steps:
|
||
- name: Setup Scripts
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }}
|
||
parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }}
|
||
safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Mask OTLP telemetry headers
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.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 || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'gpt-5.6-sol' }}"
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AGENT_VERSION: "1.0.80"
|
||
GH_AW_INFO_CLI_VERSION: "v0.88.7"
|
||
GH_AW_INFO_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_INFO_EXPERIMENTAL: "false"
|
||
GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true"
|
||
GH_AW_INFO_STAGED: "false"
|
||
GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]'
|
||
GH_AW_INFO_FIREWALL_ENABLED: "true"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_AWMG_VERSION: ""
|
||
GH_AW_INFO_FIREWALL_TYPE: "squid"
|
||
GH_AW_INFO_AGENT_RUNTIME: ""
|
||
GH_AW_COMPILED_STRICT: "true"
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'generate_aw_info.cjs'));
|
||
await main(core, context);
|
||
- name: Restore daily AIC usage cache
|
||
id: restore-daily-aic-cache
|
||
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||
continue-on-error: true
|
||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
with:
|
||
key: agentic-workflow-usage-devopshealthcheck-${{ github.run_id }}
|
||
restore-keys: agentic-workflow-usage-devopshealthcheck-
|
||
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
|
||
- name: Restore daily AIC usage cache (artifact fallback)
|
||
id: restore-daily-aic-cache-fallback
|
||
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_RESTORE_DAILY_AIC_CACHE_HIT: ${{ steps.restore-daily-aic-cache.outputs.cache-hit }}
|
||
GH_AW_RESTORE_DAILY_AIC_CACHE_MATCHED_KEY: ${{ steps.restore-daily-aic-cache.outputs.cache-matched-key }}
|
||
with:
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'restore_aic_usage_cache_fallback.cjs'));
|
||
await main();
|
||
- name: Check daily workflow token guardrail
|
||
id: daily-effective-workflow-guardrail
|
||
if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_ID: "devops-health-check"
|
||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }}
|
||
GH_AW_HAS_SLASH_COMMAND: "false"
|
||
GH_AW_HAS_LABEL_COMMAND: "false"
|
||
GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }}
|
||
with:
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'check_daily_aic_workflow_guardrail.cjs'));
|
||
await main();
|
||
- name: Check for OAuth tokens
|
||
id: check-oauth-tokens
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
|
||
env:
|
||
COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, 'NO COPILOT PAT AVAILABLE') }}
|
||
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||
- name: Checkout .github and .agents folders
|
||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
sparse-checkout: |
|
||
.github
|
||
.agents
|
||
.claude
|
||
.codex
|
||
.gemini
|
||
.pi
|
||
sparse-checkout-cone-mode: true
|
||
fetch-depth: 1
|
||
- name: Save agent config folders for base branch restoration
|
||
env:
|
||
GH_AW_AGENT_FOLDERS: ".agents .github"
|
||
GH_AW_AGENT_FILES: "AGENTS.md"
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
|
||
- name: Check workflow lock file
|
||
id: check-lock-file
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_WORKFLOW_FILE: "devops-health-check.lock.yml"
|
||
GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}"
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'check_workflow_timestamp_api.cjs'));
|
||
await main();
|
||
- name: Check compile-agentic version
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_COMPILED_VERSION: "v0.88.7"
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'check_version_updates.cjs'));
|
||
await main();
|
||
- name: Log runtime features
|
||
if: ${{ contains(toJSON(vars), '"GH_AW_RUNTIME_FEATURES":') }}
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/log_runtime_features_summary.sh"
|
||
- name: Create prompt with built-in context
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_ACTIONS_DIR: ${{ runner.temp }}/gh-aw/actions
|
||
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
|
||
GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl
|
||
GH_AW_PROMPT_CONFIG: "{\"items\":[{\"content_env\":\"GH_AW_PROMPT_CONTENT_0000\"},{\"file\":\"xpia.md\"},{\"file\":\"temp_folder_prompt.md\"},{\"file\":\"markdown.md\"},{\"file\":\"safe_outputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0001\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0002\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0003\"},{\"file\":\"github_mcp_tools_with_safeoutputs_prompt.md\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0004\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0005\"},{\"content_env\":\"GH_AW_PROMPT_CONTENT_0006\"}]}"
|
||
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
|
||
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
|
||
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
|
||
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
|
||
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
|
||
GH_AW_PROMPT_CONTENT_0000: "<system>\n"
|
||
GH_AW_PROMPT_CONTENT_0001: "<safe-output-tools>\nTools: missing_tool, missing_data, noop, publish_health_report\n"
|
||
GH_AW_PROMPT_CONTENT_0002: "</safe-output-tools>\n"
|
||
GH_AW_PROMPT_CONTENT_0003: "<github-context>\nThe following GitHub context information is available for this workflow:\n{{#if github.actor}}\n- **actor**: __GH_AW_GITHUB_ACTOR__\n{{/if}}\n{{#if github.repository}}\n- **repository**: __GH_AW_GITHUB_REPOSITORY__\n{{/if}}\n{{#if github.workspace}}\n- **workspace**: __GH_AW_GITHUB_WORKSPACE__\n{{/if}}\n{{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}\n- **issue-number**: #__GH_AW_EXPR_802A9F6A__\n{{/if}}\n{{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}\n- **discussion-number**: #__GH_AW_EXPR_1A3A194A__\n{{/if}}\n{{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}\n- **pull-request-number**: #__GH_AW_EXPR_463A214A__\n{{/if}}\n{{#if github.event.comment.id || github.aw.context.comment_id}}\n- **comment-id**: __GH_AW_EXPR_FF1D34CE__\n{{/if}}\n{{#if github.run_id}}\n- **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__\n{{/if}}\n</github-context>\n\n"
|
||
GH_AW_PROMPT_CONTENT_0004: "</system>\n"
|
||
GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/aw/shared/devops-health.lock.md}}\n"
|
||
GH_AW_PROMPT_CONTENT_0006: "{{#runtime-import .github/workflows/devops-health-check.md}}\n"
|
||
with:
|
||
script: |
|
||
const { setupGlobals } = require(process.env.GH_AW_ACTIONS_DIR + '/setup_globals.cjs');
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(process.env.GH_AW_ACTIONS_DIR + '/create_prompt.cjs');
|
||
await main(core);
|
||
- name: Interpolate variables and render templates
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
|
||
GH_AW_ENGINE_ID: "copilot"
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'interpolate_prompt.cjs'));
|
||
await main();
|
||
- name: Substitute placeholders
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
|
||
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
|
||
GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }}
|
||
GH_AW_GITHUB_ACTOR: ${{ github.actor }}
|
||
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
|
||
GH_AW_GITHUB_RUN_ID: ${{ github.run_id }}
|
||
GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }}
|
||
GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: ${{ needs.pat_pool.outputs.pat_number }}
|
||
GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }}
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
|
||
const substitutePlaceholders = require(path.join(actionsDir, 'substitute_placeholders.cjs'));
|
||
|
||
// Call the substitution function
|
||
return await substitutePlaceholders({
|
||
file: process.env.GH_AW_PROMPT,
|
||
substitutions: {
|
||
GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A,
|
||
GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A,
|
||
GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A,
|
||
GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE,
|
||
GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR,
|
||
GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY,
|
||
GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID,
|
||
GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE,
|
||
GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER: process.env.GH_AW_NEEDS_PAT_POOL_OUTPUTS_PAT_NUMBER,
|
||
GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED
|
||
}
|
||
});
|
||
- name: Validate prompt placeholders
|
||
env:
|
||
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
|
||
- name: Print prompt
|
||
env:
|
||
GH_AW_PROMPT: ${{ runner.temp }}/gh-aw/aw-prompts/prompt.txt
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
|
||
- name: Stage prompt files for artifact upload
|
||
run: |
|
||
mkdir -p /tmp/gh-aw/aw-prompts
|
||
cp -a "${RUNNER_TEMP}/gh-aw/aw-prompts/." /tmp/gh-aw/aw-prompts/
|
||
- name: Upload activation artifact
|
||
if: success() || failure()
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: activation
|
||
include-hidden-files: true
|
||
path: |
|
||
/tmp/gh-aw/aw_info.json
|
||
/tmp/gh-aw/models.json
|
||
/tmp/gh-aw/aw-prompts/prompt.txt
|
||
/tmp/gh-aw/aw-prompts/prompt-template.txt
|
||
/tmp/gh-aw/aw-prompts/prompt-import-tree.json
|
||
/tmp/gh-aw/github_rate_limits.jsonl
|
||
/tmp/gh-aw/base
|
||
/tmp/gh-aw/.github/agents
|
||
/tmp/gh-aw/.github/skills
|
||
if-no-files-found: ignore
|
||
retention-days: 1
|
||
|
||
agent:
|
||
needs:
|
||
- activation
|
||
- pat_pool
|
||
if: needs.activation.outputs.daily_ai_credits_exceeded != 'true'
|
||
runs-on: ubuntu-latest
|
||
environment: copilot-pat-pool
|
||
permissions:
|
||
actions: read
|
||
contents: read
|
||
issues: read
|
||
concurrency:
|
||
group: "gh-aw-copilot-${{ github.workflow }}"
|
||
queue: max
|
||
timeout-minutes: 60
|
||
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_PR_HEAD_BASE_BRANCH: ""
|
||
GH_AW_PR_HEAD_BASE_PR_NUMBER: ""
|
||
GH_AW_PR_HEAD_BASE_REF: ""
|
||
GH_AW_PR_HEAD_BASE_REPO: ""
|
||
GH_AW_PR_HEAD_BASE_SHA: ""
|
||
GH_AW_PR_HEAD_REPO: ""
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
GH_AW_WORKFLOW_ID_SANITIZED: devopshealthcheck
|
||
outputs:
|
||
agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }}
|
||
ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }}
|
||
aic: ${{ steps.parse-mcp-gateway.outputs.aic }}
|
||
ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }}
|
||
checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }}
|
||
effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }}
|
||
has_patch: ${{ steps.collect_output.outputs.has_patch }}
|
||
http_400_response_error: ${{ steps.detect-agent-errors.outputs.http_400_response_error || 'false' }}
|
||
inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }}
|
||
invocation_cap_exceeded: ${{ steps.detect-agent-errors.outputs.invocation_cap_exceeded || 'false' }}
|
||
max_cache_misses_exceeded: ${{ steps.detect-agent-errors.outputs.max_cache_misses_exceeded || 'false' }}
|
||
mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }}
|
||
missing_model_pricing_error: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_error || 'false' }}
|
||
missing_model_pricing_model_name: ${{ steps.detect-agent-errors.outputs.missing_model_pricing_model_name || '' }}
|
||
model: ${{ needs.activation.outputs.model }}
|
||
model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }}
|
||
output: ${{ steps.collect_output.outputs.output }}
|
||
output_types: ${{ steps.collect_output.outputs.output_types }}
|
||
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
|
||
setup-span-id: ${{ steps.setup.outputs.span-id }}
|
||
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
|
||
shell_expansion_guard_rejected: ${{ steps.detect-agent-errors.outputs.shell_expansion_guard_rejected || 'false' }}
|
||
unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }}
|
||
steps:
|
||
- name: Setup Scripts
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Set runtime paths
|
||
id: set-runtime-paths
|
||
env:
|
||
GH_AW_RUNNER_TOOL_CACHE: ${{ runner.tool_cache }}
|
||
run: |
|
||
if [ -z "${RUNNER_TOOL_CACHE:-}" ]; then
|
||
echo "RUNNER_TOOL_CACHE=${GH_AW_RUNNER_TOOL_CACHE}" >> "$GITHUB_ENV"
|
||
fi
|
||
{
|
||
echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl"
|
||
echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json"
|
||
echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json"
|
||
} >> "$GITHUB_OUTPUT"
|
||
- name: Mask OTLP telemetry headers
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
|
||
- name: Check OTLP telemetry configuration
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_otlp_default_credentials.sh"
|
||
- name: Checkout repository
|
||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
- name: Create gh-aw temp directory
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh"
|
||
- name: Configure gh CLI for GitHub Enterprise
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh"
|
||
env:
|
||
GH_TOKEN: ${{ github.token }}
|
||
- name: Download activation artifact
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
name: activation
|
||
path: /tmp/gh-aw
|
||
- name: Configure Git credentials
|
||
env:
|
||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
|
||
- name: Checkout PR branch
|
||
id: checkout-pr
|
||
if: |
|
||
github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request'
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'checkout_pr_branch.cjs'));
|
||
await main();
|
||
- name: Install GitHub Copilot CLI
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
|
||
env:
|
||
GH_HOST: github.com
|
||
GH_AW_COMPILED_VERSION: v0.88.7
|
||
- name: Install AWF binary
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.14 --rootless
|
||
- name: Determine automatic lockdown mode for GitHub MCP Server
|
||
id: determine-automatic-lockdown
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
|
||
env:
|
||
GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||
GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const determineAutomaticLockdown = require(path.join(actionsDir, 'determine_automatic_lockdown.cjs'));
|
||
await determineAutomaticLockdown(github, context, core);
|
||
- name: Restore agent config folders from base branch
|
||
if: steps.checkout-pr.outcome == 'success'
|
||
env:
|
||
GH_AW_AGENT_FOLDERS: ".agents .github"
|
||
GH_AW_AGENT_FILES: "AGENTS.md"
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh"
|
||
- name: Restore inline sub-agents from activation artifact
|
||
env:
|
||
GH_AW_SUB_AGENT_DIR: ".github/agents"
|
||
GH_AW_SUB_AGENT_EXT: ".agent.md"
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh"
|
||
- name: Restore inline skills from activation artifact
|
||
env:
|
||
GH_AW_SKILL_DIR: ".github/skills"
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh"
|
||
- name: Download container images
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5 ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5 ghcr.io/github/gh-aw-mcpg:v0.4.18@sha256:85b940556a8faa4e1fdbef124bfd75f2c4ebd855a10b88a1c3b6f3e97f6f1a53 ghcr.io/github/gh-aw-node@sha256:33e1ec1d967ac1f28c2cedc24ce103dea3226840626de345d3fe579e96cf5c7d ghcr.io/github/github-mcp-server:v1.11.0@sha256:fbec75de11c255213fa08d80fb166abe73d851fff631c51c0079872967720699
|
||
- name: Prepare Safe Outputs Directories
|
||
run: |
|
||
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
|
||
mkdir -p /tmp/gh-aw/safeoutputs
|
||
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
|
||
- name: Generate Safe Outputs Config
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_FILE_ROOT: "${{ runner.temp }}/gh-aw"
|
||
GH_AW_FILE_CONFIG: "{\"files\":[{\"path\":\"safeoutputs/config.json\",\"content_env\":\"GH_AW_SAFE_OUTPUTS_CONFIG\"}]}"
|
||
GH_AW_SAFE_OUTPUTS_CONFIG: "{\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"publish-health-report\":{\"description\":\"Persist dashboard state, then comment and dispatch investigations\",\"inputs\":{\"body\":{\"default\":null,\"description\":\"Complete validated replacement body for issue 695\",\"required\":true,\"type\":\"string\"},\"comment_body\":{\"default\":null,\"description\":\"Daily audit comment body\",\"required\":true,\"type\":\"string\"},\"dispatches_json\":{\"default\":null,\"description\":\"At most two investigator inputs as one exact fenced JSON block\",\"required\":true,\"type\":\"string\"},\"investigation_rows_json\":{\"default\":null,\"description\":\"Structured investigation rows as one exact fenced JSON block\",\"required\":true,\"type\":\"string\"},\"state_json\":{\"default\":null,\"description\":\"Dashboard state as one exact fenced JSON block\",\"required\":true,\"type\":\"string\"}}}}"
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'create_files.cjs'));
|
||
await main();
|
||
- name: Generate Safe Outputs Tools
|
||
env:
|
||
GH_AW_TOOLS_META_JSON: |
|
||
{
|
||
"description_suffixes": {},
|
||
"repo_params": {},
|
||
"dynamic_tools": [
|
||
{
|
||
"description": "Persist dashboard state, then comment and dispatch investigations",
|
||
"inputSchema": {
|
||
"additionalProperties": false,
|
||
"properties": {
|
||
"body": {
|
||
"description": "Complete validated replacement body for issue 695",
|
||
"type": "string"
|
||
},
|
||
"comment_body": {
|
||
"description": "Daily audit comment body",
|
||
"type": "string"
|
||
},
|
||
"dispatches_json": {
|
||
"description": "At most two investigator inputs as one exact fenced JSON block",
|
||
"type": "string"
|
||
},
|
||
"investigation_rows_json": {
|
||
"description": "Structured investigation rows as one exact fenced JSON block",
|
||
"type": "string"
|
||
},
|
||
"state_json": {
|
||
"description": "Dashboard state as one exact fenced JSON block",
|
||
"type": "string"
|
||
}
|
||
},
|
||
"required": [
|
||
"body",
|
||
"comment_body",
|
||
"dispatches_json",
|
||
"investigation_rows_json",
|
||
"state_json"
|
||
],
|
||
"type": "object"
|
||
},
|
||
"name": "publish_health_report"
|
||
}
|
||
]
|
||
}
|
||
GH_AW_VALIDATION_JSON: |
|
||
{
|
||
"missing_data": {
|
||
"defaultMax": 20,
|
||
"fields": {
|
||
"alternatives": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 256
|
||
},
|
||
"context": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 256
|
||
},
|
||
"data_type": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 128
|
||
},
|
||
"reason": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 256
|
||
}
|
||
}
|
||
},
|
||
"missing_tool": {
|
||
"defaultMax": 20,
|
||
"fields": {
|
||
"alternatives": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 512
|
||
},
|
||
"reason": {
|
||
"required": true,
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 256
|
||
},
|
||
"tool": {
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 128
|
||
}
|
||
}
|
||
},
|
||
"noop": {
|
||
"defaultMax": 1,
|
||
"fields": {
|
||
"message": {
|
||
"required": true,
|
||
"type": "string",
|
||
"sanitize": true,
|
||
"maxLength": 65000
|
||
}
|
||
}
|
||
}
|
||
}
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'generate_safe_outputs_tools.cjs'));
|
||
await main();
|
||
- name: Start MCP Gateway
|
||
id: start-mcp-gateway
|
||
env:
|
||
GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST: ${{ vars.GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST || 'true' }}
|
||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||
GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }}
|
||
GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }}
|
||
GH_AW_SINK_VISIBILITY: ${{ steps.determine-automatic-lockdown.outputs.visibility }}
|
||
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 }}
|
||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
run: |
|
||
set -eo pipefail
|
||
mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config"
|
||
if [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -r "${GITHUB_EVENT_PATH}" ]; then
|
||
GH_AW_SAFEOUTPUTS_EVENT_PATH="${RUNNER_TEMP}/gh-aw/safeoutputs/github_event.json"
|
||
cp "${GITHUB_EVENT_PATH}" "${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
|
||
export GITHUB_EVENT_PATH="${GH_AW_SAFEOUTPUTS_EVENT_PATH}"
|
||
fi
|
||
|
||
# Export gateway environment variables for MCP config and gateway script
|
||
export MCP_GATEWAY_PORT="8080"
|
||
export MCP_GATEWAY_DOMAIN="awmg-mcpg"
|
||
export MCP_GATEWAY_HOST_DOMAIN="localhost"
|
||
MCP_GATEWAY_AGENT_ID=$(openssl rand -base64 45 | tr -d '/+=')
|
||
echo "::add-mask::${MCP_GATEWAY_AGENT_ID}"
|
||
export MCP_GATEWAY_AGENT_ID
|
||
export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads"
|
||
mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}"
|
||
export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288"
|
||
export MCP_GATEWAY_ALLOWED_MOUNT_ROOTS="${GITHUB_WORKSPACE}:rw,${RUNNER_TEMP}/gh-aw:ro,${RUNNER_TEMP}/gh-aw/safeoutputs:rw,/opt:ro,/tmp:rw,/usr/bin/gh:ro"
|
||
export GH_AW_PR_HEAD_BASE_BRANCH="${GH_AW_PR_HEAD_BASE_BRANCH:-}"
|
||
export GH_AW_PR_HEAD_BASE_SHA="${GH_AW_PR_HEAD_BASE_SHA:-}"
|
||
export GH_AW_PR_HEAD_BASE_REPO="${GH_AW_PR_HEAD_BASE_REPO:-}"
|
||
export GH_AW_PR_HEAD_BASE_PR_NUMBER="${GH_AW_PR_HEAD_BASE_PR_NUMBER:-}"
|
||
export GH_AW_PR_HEAD_BASE_REF="${GH_AW_PR_HEAD_BASE_REF:-}"
|
||
export GH_AW_PR_HEAD_REPO="${GH_AW_PR_HEAD_REPO:-}"
|
||
export DEBUG="*"
|
||
|
||
export GH_AW_ENGINE="copilot"
|
||
MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0')
|
||
MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0')
|
||
source "${RUNNER_TEMP}/gh-aw/actions/resolve_docker_socket_gid.sh"
|
||
export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network bridge -p 127.0.0.1:'"${MCP_GATEWAY_PORT}"':'"${MCP_GATEWAY_PORT}"' --name awmg-mcpg --add-host host.docker.internal:host-gateway --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_AGENT_ID -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_PR_HEAD_BASE_BRANCH -e GH_AW_PR_HEAD_BASE_SHA -e GH_AW_PR_HEAD_BASE_REPO -e GH_AW_PR_HEAD_BASE_PR_NUMBER -e GH_AW_PR_HEAD_BASE_REF -e GH_AW_PR_HEAD_REPO -e GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GH_AW_SINK_VISIBILITY -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -e RUNNER_TOOL_CACHE -e MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -e GITHUB_AW_OTEL_TRACE_ID -e GITHUB_AW_OTEL_PARENT_SPAN_ID -e OTEL_EXPORTER_OTLP_HEADERS -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.4.18'
|
||
|
||
mkdir -p "$HOME/.copilot"
|
||
GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node)
|
||
cat << GH_AW_MCP_CONFIG_b48e4c5b570fec9e_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
|
||
{
|
||
"mcpServers": {
|
||
"github": {
|
||
"type": "stdio",
|
||
"container": "ghcr.io/github/github-mcp-server:v1.11.0",
|
||
"env": {
|
||
"GITHUB_FEATURES": "fields_param",
|
||
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
|
||
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_MCP_SERVER_TOKEN}",
|
||
"GITHUB_READ_ONLY": "1",
|
||
"GITHUB_TOOLSETS": "repos,issues,actions"
|
||
},
|
||
"guard-policies": {
|
||
"allow-only": {
|
||
"min-integrity": "$GITHUB_MCP_GUARD_MIN_INTEGRITY",
|
||
"repos": "$GITHUB_MCP_GUARD_REPOS"
|
||
}
|
||
}
|
||
},
|
||
"safeoutputs": {
|
||
"type": "stdio",
|
||
"container": "ghcr.io/github/gh-aw-node",
|
||
"mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"],
|
||
"args": ["-w", "\${GITHUB_WORKSPACE}"],
|
||
"entrypoint": "sh",
|
||
"entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"],
|
||
"env": {
|
||
"DEBUG": "*",
|
||
"DEFAULT_BRANCH": "\${DEFAULT_BRANCH}",
|
||
"GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
|
||
"GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
|
||
"GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
|
||
"GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}",
|
||
"GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}",
|
||
"GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}",
|
||
"GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}",
|
||
"GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST": "\${GH_AW_POLICY_ALLOW_CREATE_PULL_REQUEST}",
|
||
"GH_AW_PR_HEAD_BASE_BRANCH": "\${GH_AW_PR_HEAD_BASE_BRANCH}",
|
||
"GH_AW_PR_HEAD_BASE_SHA": "\${GH_AW_PR_HEAD_BASE_SHA}",
|
||
"GH_AW_PR_HEAD_BASE_REPO": "\${GH_AW_PR_HEAD_BASE_REPO}",
|
||
"GH_AW_PR_HEAD_BASE_PR_NUMBER": "\${GH_AW_PR_HEAD_BASE_PR_NUMBER}",
|
||
"GH_AW_PR_HEAD_BASE_REF": "\${GH_AW_PR_HEAD_BASE_REF}",
|
||
"GH_AW_PR_HEAD_REPO": "\${GH_AW_PR_HEAD_REPO}",
|
||
"GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
|
||
"GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
|
||
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
|
||
"GITHUB_SHA": "\${GITHUB_SHA}",
|
||
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
|
||
"GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}",
|
||
"RUNNER_TEMP": "\${RUNNER_TEMP}"
|
||
},
|
||
"guard-policies": {
|
||
"write-sink": {
|
||
"accept": [
|
||
"*"
|
||
],
|
||
"sink-visibility": "${GH_AW_SINK_VISIBILITY}"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
"gateway": {
|
||
"port": $MCP_GATEWAY_PORT,
|
||
"domain": "${MCP_GATEWAY_DOMAIN}",
|
||
"agentId": "${MCP_GATEWAY_AGENT_ID}",
|
||
"payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}",
|
||
"startupTimeout": 120,
|
||
"opentelemetry": {
|
||
"endpoint": "${OTEL_EXPORTER_OTLP_ENDPOINT}",
|
||
"traceId": "${GITHUB_AW_OTEL_TRACE_ID}",
|
||
"spanId": "${GITHUB_AW_OTEL_PARENT_SPAN_ID}"
|
||
}
|
||
}
|
||
}
|
||
GH_AW_MCP_CONFIG_b48e4c5b570fec9e_EOF
|
||
- name: Mount MCP servers as CLIs
|
||
id: mount-mcp-clis
|
||
continue-on-error: true
|
||
env:
|
||
MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
|
||
MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }}
|
||
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io);
|
||
const { main } = require(path.join(actionsDir, 'mount_mcp_as_cli.cjs'));
|
||
await main();
|
||
- name: Clean credentials
|
||
continue-on-error: true
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh"
|
||
- name: Audit pre-agent workspace
|
||
id: pre_agent_audit
|
||
continue-on-error: true
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh"
|
||
- name: Execute GitHub Copilot CLI
|
||
id: agentic_execution
|
||
# Copilot CLI tool arguments (sorted):
|
||
# --allow-tool github
|
||
# --allow-tool safeoutputs
|
||
timeout-minutes: 60
|
||
run: |
|
||
set -o pipefail
|
||
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
|
||
trap 'gh_aw_exit_code=$?; mkdir -p /tmp/gh-aw >/dev/null 2>&1 || true; printf "%s" "$gh_aw_exit_code" > /tmp/gh-aw/agent_execution_exit_code.txt || true; rm -f "$HOME/.copilot/settings.json"; if [ "$gh_aw_exit_code" -ne 0 ]; then echo "::error::Agent execution exited with code $gh_aw_exit_code"; fi' EXIT
|
||
mkdir -p "$HOME/.copilot"
|
||
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
|
||
export XDG_CONFIG_HOME="$HOME"
|
||
export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json"
|
||
GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
|
||
if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
|
||
echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
|
||
exit 127
|
||
fi
|
||
GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
|
||
mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
|
||
if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
|
||
cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
|
||
fi
|
||
chmod 755 "$GH_AW_COPILOT_BIN"
|
||
|
||
touch /tmp/gh-aw/agent-step-summary.md
|
||
GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true)
|
||
export GH_AW_NODE_BIN
|
||
export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK"
|
||
(umask 177 && touch /tmp/gh-aw/agent-stdio.log)
|
||
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-1000}"
|
||
if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
|
||
GH_AW_MAX_AI_CREDITS="1000"
|
||
fi
|
||
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.14/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\"],\"isolation\":true,\"topologyAttach\":[\"awmg-mcpg\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.14,squid=sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5,agent=sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98,api-proxy=sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5,cli-proxy=sha256:3a379c5e96e29499c815e9dd2a71334d01c326a9b73991c76544fda9cae35c34\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
|
||
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
|
||
GH_AW_DOCKER_HOST=""
|
||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||
GH_AW_DOCKER_HOST="${DOCKER_HOST}"
|
||
fi
|
||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||
GH_AW_CHROOT_BINARIES_SOURCE_PATH="${RUNNER_TEMP}/gh-aw" GH_AW_CHROOT_IDENTITY_HOME="${RUNNER_TEMP}/gh-aw/home" node "${RUNNER_TEMP}/gh-aw/actions/patch_awf_chroot_config.cjs"
|
||
fi
|
||
GH_AW_TOOL_CACHE_MOUNT=""
|
||
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
|
||
if [ -d "$GH_AW_TOOL_CACHE" ]; then
|
||
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
|
||
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
|
||
fi
|
||
fi
|
||
# shellcheck disable=SC1003,SC2016,SC2086
|
||
GH_AW_AWF_ENGINE_NAME=copilot \
|
||
GH_AW_AWF_HARNESS_MARKER='[copilot-harness]' \
|
||
GH_AW_AWF_LOG_FILE=/tmp/gh-aw/agent-stdio.log \
|
||
GH_AW_AWF_ATTEMPT_LOG_NAME=copilot \
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/run_awf_with_startup_retries.sh" -- \
|
||
awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_AGENT_ID --mount /tmp/gh-aw:/tmp/gh-aw:rw --log-level info --skip-pull \
|
||
-- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; GH_AW_TOOL_BINS="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')"; GH_AW_TOOL_BINS="${GH_AW_TOOL_BINS%:}"; export PATH="$PATH${GH_AW_TOOL_BINS:+:}$GH_AW_TOOL_BINS"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" "${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs" "${RUNNER_TEMP}/gh-aw/bin/copilot" --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt'
|
||
env:
|
||
AWF_REFLECT_ENABLED: 1
|
||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||
COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, 'NO COPILOT PAT AVAILABLE') }}
|
||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'gpt-5.6-sol' }}
|
||
GH_AW_LLM_PROVIDER: github
|
||
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
|
||
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
|
||
GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
|
||
GH_AW_PHASE: agent
|
||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||
GH_AW_TIMEOUT_MINUTES: 60
|
||
GH_AW_VERSION: v0.88.7
|
||
GITHUB_API_URL: ${{ github.api_url }}
|
||
GITHUB_AW: true
|
||
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
|
||
GITHUB_HEAD_REF: ${{ github.head_ref }}
|
||
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
|
||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
||
GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
|
||
GIT_AUTHOR_NAME: github-actions[bot]
|
||
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
|
||
GIT_COMMITTER_NAME: github-actions[bot]
|
||
RUNNER_TEMP: ${{ runner.temp }}
|
||
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
|
||
- name: Detect agent errors
|
||
if: always()
|
||
id: detect-agent-errors
|
||
continue-on-error: true
|
||
env:
|
||
GH_AW_AGENTIC_EXECUTION_OUTCOME: ${{ steps.agentic_execution.outcome }}
|
||
GH_AW_ENGINE_STEP_TIMEOUT_MINUTES: 60
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'detect_agent_errors.cjs'));
|
||
await main();
|
||
- name: Configure Git credentials
|
||
env:
|
||
GITHUB_REPOSITORY: ${{ github.repository }}
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_TOKEN: ${{ github.token }}
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh"
|
||
- name: Copy Copilot session state files to logs
|
||
if: always()
|
||
continue-on-error: true
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh"
|
||
- name: Stop MCP Gateway
|
||
if: always()
|
||
continue-on-error: true
|
||
env:
|
||
MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }}
|
||
MCP_GATEWAY_AGENT_ID: ${{ steps.start-mcp-gateway.outputs.gateway-agent-id }}
|
||
GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }}
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID"
|
||
- name: Redact secrets in logs
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'redact_secrets.cjs'));
|
||
await main();
|
||
env:
|
||
GH_AW_SECRET_NAMES: 'COPILOT_PAT_0,COPILOT_PAT_1,COPILOT_PAT_2,COPILOT_PAT_3,COPILOT_PAT_4,COPILOT_PAT_5,COPILOT_PAT_6,COPILOT_PAT_7,COPILOT_PAT_8,COPILOT_PAT_9,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN'
|
||
SECRET_COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }}
|
||
SECRET_COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }}
|
||
SECRET_COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }}
|
||
SECRET_COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }}
|
||
SECRET_COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }}
|
||
SECRET_COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }}
|
||
SECRET_COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }}
|
||
SECRET_COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }}
|
||
SECRET_COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }}
|
||
SECRET_COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }}
|
||
SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }}
|
||
SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }}
|
||
SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||
- name: Append agent step summary
|
||
if: always()
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh"
|
||
- name: Copy Safe Outputs
|
||
if: always()
|
||
env:
|
||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||
run: |
|
||
mkdir -p /tmp/gh-aw
|
||
cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true
|
||
- name: Ingest agent output
|
||
id: collect_output
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||
GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_API_URL: ${{ github.api_url }}
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'collect_ndjson_output.cjs'));
|
||
await main();
|
||
- name: Parse agent logs for step summary
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/
|
||
GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }}
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'parse_copilot_log.cjs'));
|
||
await main();
|
||
- name: Parse MCP Gateway logs for step summary
|
||
if: always()
|
||
id: parse-mcp-gateway
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, '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: bash "${RUNNER_TEMP}/gh-aw/actions/print_firewall_logs.sh" --rootless
|
||
- name: Parse token usage for step summary
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
|
||
await main();
|
||
- name: Print AWF reflect summary
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'awf_reflect_summary.cjs'));
|
||
await main();
|
||
- name: Generate observability summary
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'generate_observability_summary.cjs'));
|
||
await main(core);
|
||
- 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
|
||
# Small dedicated copy of the agent output so safe-output processing
|
||
# survives a failed or timed-out upload of the larger agent artifact
|
||
- name: Upload agent output fallback artifact
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: agent-output-fallback
|
||
path: |
|
||
/tmp/gh-aw/agent_output.json
|
||
/tmp/gh-aw/safeoutputs.jsonl
|
||
if-no-files-found: ignore
|
||
- name: Upload agent artifacts
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: agent
|
||
path: |
|
||
/tmp/gh-aw/aw-prompts/prompt.txt
|
||
/tmp/gh-aw/sandbox/agent/logs/
|
||
/tmp/gh-aw/redacted-urls.log
|
||
/tmp/gh-aw/mcp-logs/
|
||
/tmp/gh-aw/agent_usage.json
|
||
/tmp/gh-aw/agent-stdio.log
|
||
/tmp/gh-aw/pre-agent-audit.txt
|
||
/tmp/gh-aw/github_rate_limits.jsonl
|
||
/tmp/gh-aw/otel.jsonl
|
||
/tmp/gh-aw/otlp-export-errors.jsonl
|
||
/tmp/gh-aw/safeoutputs.jsonl
|
||
/tmp/gh-aw/agent_output.json
|
||
/tmp/gh-aw/aw-*.patch
|
||
/tmp/gh-aw/aw-*.bundle
|
||
/tmp/gh-aw/awf-config.json
|
||
/tmp/gh-aw/sandbox/firewall/logs/
|
||
/tmp/gh-aw/sandbox/firewall/audit/
|
||
/tmp/gh-aw/sandbox/firewall/awf-reflect.json
|
||
if-no-files-found: ignore
|
||
|
||
conclusion:
|
||
needs:
|
||
- activation
|
||
- agent
|
||
- detection
|
||
- pat_pool
|
||
- publish_health_report
|
||
- safe_outputs
|
||
if: >
|
||
always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' ||
|
||
needs.activation.outputs.oauth_token_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' ||
|
||
needs.activation.outputs.daily_ai_credits_exceeded == 'true')
|
||
runs-on: ubuntu-slim
|
||
environment: copilot-pat-pool
|
||
permissions:
|
||
actions: write
|
||
issues: write
|
||
concurrency:
|
||
group: "gh-aw-conclusion-devops-health-check"
|
||
cancel-in-progress: false
|
||
queue: max
|
||
env:
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
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
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Download agent output artifact
|
||
id: download-agent-output
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: "{agent,agent-output-fallback}"
|
||
merge-multiple: true
|
||
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
|
||
if [ -f "/tmp/gh-aw/agent_output.json" ]; then
|
||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
- name: Download detection artifact
|
||
id: download-detection-artifact
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
name: detection
|
||
path: /tmp/gh-aw/threat-detection/
|
||
- name: Download Safe Outputs Items Manifest
|
||
id: download-safe-outputs-manifest
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: safe-outputs-items
|
||
merge-multiple: true
|
||
path: /tmp/gh-aw/
|
||
- name: Collect usage artifact files
|
||
if: always()
|
||
continue-on-error: true
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/collect_usage_artifact_files.sh"
|
||
- name: Upload usage artifact
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: usage
|
||
path: |
|
||
/tmp/gh-aw/usage/aw_info.json
|
||
/tmp/gh-aw/usage/aw-info.jsonl
|
||
/tmp/gh-aw/usage/agent_usage.json
|
||
/tmp/gh-aw/usage/agent_usage.jsonl
|
||
/tmp/gh-aw/usage/detection_usage.jsonl
|
||
/tmp/gh-aw/usage/evals.jsonl
|
||
/tmp/gh-aw/usage/graders/grader_manifest.json
|
||
/tmp/gh-aw/usage/graders/grader_results.json
|
||
/tmp/gh-aw/usage/github_rate_limits.jsonl
|
||
/tmp/gh-aw/usage/agent/token_usage.jsonl
|
||
/tmp/gh-aw/usage/detection/token_usage.jsonl
|
||
/tmp/gh-aw/usage/activity/summary.json
|
||
if-no-files-found: ignore
|
||
- name: Restore daily AIC usage cache
|
||
id: restore-daily-aic-cache-conclusion
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
with:
|
||
key: agentic-workflow-usage-devopshealthcheck-${{ github.run_id }}
|
||
restore-keys: agentic-workflow-usage-devopshealthcheck-
|
||
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
|
||
- name: Write daily AIC usage cache entry
|
||
id: write-daily-aic-cache
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
github-token: ${{ github.token }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context);
|
||
const { main } = require(path.join(actionsDir, 'write_daily_aic_usage_cache.cjs'));
|
||
await main();
|
||
- name: Save daily AIC usage cache
|
||
id: save-daily-aic-cache
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
||
with:
|
||
key: agentic-workflow-usage-devopshealthcheck-${{ github.run_id }}
|
||
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
|
||
- name: Upload daily AIC usage cache artifact
|
||
id: upload-daily-aic-cache
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: aic-usage-cache
|
||
path: /tmp/gh-aw/agentic-workflow-usage-cache.jsonl
|
||
if-no-files-found: ignore
|
||
retention-days: 7
|
||
- name: Process no-op messages
|
||
id: noop
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_NOOP_MAX: "1"
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||
GH_AW_NOOP_REPORT_AS_ISSUE: "false"
|
||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
|
||
GH_AW_WORKFLOW_ID: "devops-health-check"
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'handle_noop_message.cjs'));
|
||
await main();
|
||
- name: Log detection run
|
||
id: detection_runs
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'handle_detection_runs.cjs'));
|
||
await main();
|
||
- name: Record missing tool
|
||
id: missing_tool
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_MISSING_TOOL_CREATE_ISSUE: "true"
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'missing_tool.cjs'));
|
||
await main();
|
||
- name: Handle agent failure
|
||
id: handle_agent_failure
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }}
|
||
GH_AW_WORKFLOW_ID: "devops-health-check"
|
||
GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "0"
|
||
GH_AW_ENGINE_ID: "copilot"
|
||
GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }}
|
||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }}
|
||
GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }}
|
||
GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }}
|
||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}
|
||
GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }}
|
||
GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }}
|
||
GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }}
|
||
GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }}
|
||
GH_AW_HTTP_400_RESPONSE_ERROR: ${{ needs.agent.outputs.http_400_response_error }}
|
||
GH_AW_MAX_CACHE_MISSES_EXCEEDED: ${{ needs.agent.outputs.max_cache_misses_exceeded }}
|
||
GH_AW_MISSING_MODEL_PRICING_ERROR: ${{ needs.agent.outputs.missing_model_pricing_error }}
|
||
GH_AW_MISSING_MODEL_PRICING_MODEL_NAME: ${{ needs.agent.outputs.missing_model_pricing_model_name }}
|
||
GH_AW_SHELL_EXPANSION_GUARD_REJECTED: ${{ needs.agent.outputs.shell_expansion_guard_rejected }}
|
||
GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com"
|
||
GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }}
|
||
GH_AW_OAUTH_TOKEN_CHECK_FAILED: ${{ needs.activation.outputs.oauth_token_check_failed }}
|
||
GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }}
|
||
GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }}
|
||
GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }}
|
||
GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }}
|
||
GH_AW_GROUP_REPORTS: "false"
|
||
GH_AW_FAILURE_REPORT_AS_ISSUE: "false"
|
||
GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true"
|
||
GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true"
|
||
GH_AW_TIMEOUT_MINUTES: "60"
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'handle_agent_failure.cjs'));
|
||
await main();
|
||
- name: Report failed jobs
|
||
id: report_failed_jobs
|
||
if: always()
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||
GH_AW_REPORT_FAILED_JOBS: "true"
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'report_failed_jobs.cjs'));
|
||
await main();
|
||
|
||
detection:
|
||
needs:
|
||
- activation
|
||
- agent
|
||
- pat_pool
|
||
if: always() && needs.agent.result != 'skipped'
|
||
runs-on: ubuntu-latest
|
||
environment: copilot-pat-pool
|
||
permissions:
|
||
contents: read
|
||
timeout-minutes: 10
|
||
env:
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
outputs:
|
||
aic: ${{ steps.parse_detection_token_usage.outputs.aic }}
|
||
detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }}
|
||
detection_reason: ${{ steps.detection_conclusion.outputs.reason }}
|
||
detection_success: ${{ steps.detection_conclusion.outputs.success }}
|
||
steps:
|
||
- name: Setup Scripts
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Download activation artifact
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
name: activation
|
||
path: /tmp/gh-aw
|
||
- name: Download agent output artifact
|
||
id: download-agent-output
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: "{agent,agent-output-fallback}"
|
||
merge-multiple: true
|
||
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
|
||
if [ -f "/tmp/gh-aw/agent_output.json" ]; then
|
||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
- name: Checkout repository for patch context
|
||
if: needs.agent.outputs.has_patch == 'true'
|
||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||
with:
|
||
persist-credentials: false
|
||
# --- Threat Detection ---
|
||
- name: Clean stale firewall files from agent artifact
|
||
run: |
|
||
rm -rf /tmp/gh-aw/sandbox/firewall/logs
|
||
rm -rf /tmp/gh-aw/sandbox/firewall/audit
|
||
- name: Download container images
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.28.14@sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98 ghcr.io/github/gh-aw-firewall/api-proxy:0.28.14@sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5 ghcr.io/github/gh-aw-firewall/squid:0.28.14@sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5
|
||
- name: Check if detection needed
|
||
id: detection_guard
|
||
if: always()
|
||
env:
|
||
OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }}
|
||
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
|
||
run: |
|
||
if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then
|
||
echo "run_detection=true" >> "$GITHUB_OUTPUT"
|
||
echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH"
|
||
else
|
||
echo "run_detection=false" >> "$GITHUB_OUTPUT"
|
||
echo "Detection skipped: no agent outputs or patches to analyze"
|
||
fi
|
||
- name: Clear MCP Config for detection
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
run: |
|
||
rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json"
|
||
rm -f "$HOME/.copilot/mcp-config.json"
|
||
rm -f "$GITHUB_WORKSPACE/.gemini/settings.json"
|
||
- name: Prepare threat detection files
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/prepare_threat_detection_files.sh"
|
||
- name: Setup threat detection
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
WORKFLOW_DESCRIPTION: "Orchestrator workflow that collects repo infrastructure health signals daily (pipelines, CI/CD infrastructure, resource usage), computes a fingerprint-based diff against the previous run, updates a pinned health dashboard issue, and dispatches investigation workers for new critical/warning findings. Focused on pipeline, infrastructure, and resource usage health only — does not track individual skill quality or PR review status."
|
||
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
|
||
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
|
||
GH_AW_DETECTION_SKIP_PROMPT_SUMMARY: "true"
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, '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 AWF binary
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.14 --rootless
|
||
- name: Setup Node.js
|
||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||
with:
|
||
node-version: '24'
|
||
package-manager-cache: false
|
||
- name: Install GitHub Copilot CLI
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
|
||
env:
|
||
GH_HOST: github.com
|
||
GH_AW_COMPILED_VERSION: v0.88.7
|
||
- name: Install threat-detect binary
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
continue-on-error: true
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
|
||
- name: Execute threat detection with AWF
|
||
id: detection_agentic_execution
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
continue-on-error: true
|
||
timeout-minutes: 10
|
||
env:
|
||
AWF_REFLECT_ENABLED: 1
|
||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
|
||
COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, 'NO COPILOT PAT AVAILABLE') }}
|
||
COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'gpt-5.6-sol' }}
|
||
GH_AW_HARNESS_MAX_RETRIES: 0
|
||
GH_AW_LLM_PROVIDER: github
|
||
GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}
|
||
GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }}
|
||
GH_AW_MODEL_FALLBACK: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'auto' }}
|
||
GH_AW_PHASE: detection
|
||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||
GH_AW_TIMEOUT_MINUTES: 10
|
||
GH_AW_VERSION: v0.88.7
|
||
GITHUB_API_URL: ${{ github.api_url }}
|
||
GITHUB_AW: true
|
||
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
|
||
GITHUB_HEAD_REF: ${{ github.head_ref }}
|
||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md
|
||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
||
GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com
|
||
GIT_AUTHOR_NAME: github-actions[bot]
|
||
GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com
|
||
GIT_COMMITTER_NAME: github-actions[bot]
|
||
RUNNER_TEMP: ${{ runner.temp }}
|
||
TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }}
|
||
WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
WORKFLOW_DESCRIPTION: "Orchestrator workflow that collects repo infrastructure health signals daily (pipelines, CI/CD infrastructure, resource usage), computes a fingerprint-based diff against the previous run, updates a pinned health dashboard issue, and dispatches investigation workers for new critical/warning findings. Focused on pipeline, infrastructure, and resource usage health only — does not track individual skill quality or PR review status."
|
||
HAS_PATCH: ${{ needs.agent.outputs.has_patch }}
|
||
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
|
||
run: |
|
||
set -o pipefail
|
||
printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt
|
||
GH_AW_COPILOT_SRC="$(command -v copilot 2>/dev/null || true)"
|
||
if [ -z "$GH_AW_COPILOT_SRC" ] || [ ! -x "$GH_AW_COPILOT_SRC" ]; then
|
||
echo "GitHub Copilot CLI executable not found on PATH after installation" >&2
|
||
exit 127
|
||
fi
|
||
GH_AW_COPILOT_BIN="${RUNNER_TEMP}/gh-aw/bin/copilot"
|
||
mkdir -p "${RUNNER_TEMP}/gh-aw/bin"
|
||
if [ "$GH_AW_COPILOT_SRC" != "$GH_AW_COPILOT_BIN" ]; then
|
||
cp "$GH_AW_COPILOT_SRC" "$GH_AW_COPILOT_BIN"
|
||
fi
|
||
chmod 755 "$GH_AW_COPILOT_BIN"
|
||
|
||
(umask 177 && touch /tmp/gh-aw/threat-detection/detection.log)
|
||
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
|
||
if [[ ! "$GH_AW_MAX_AI_CREDITS" =~ ^[0-9]+$ ]]; then
|
||
GH_AW_MAX_AI_CREDITS="400"
|
||
fi
|
||
printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.14/awf-config.schema.json\",\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5,\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.5\",\"gpt-5.6\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"auto\":[\"copilot/auto\",\"large\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\",\"kimi\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"detection\":[\"small\"],\"evals\":[\"small\"],\"fable\":[\"copilot/*fable*\",\"anthropic/*fable*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-3.6-flash\":[\"copilot/gemini-3.6*flash*\",\"google/gemini-3.6*flash*\",\"gemini/gemini-3.6*flash*\"],\"gemini-3.7-flash\":[\"copilot/gemini-3.7*flash*\",\"google/gemini-3.7*flash*\",\"gemini/gemini-3.7*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-omni\":[\"copilot/gemini-omni*\",\"google/gemini-omni*\",\"gemini/gemini-omni*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.1\":[\"copilot/gpt-5.1*\",\"openai/gpt-5.1*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"gpt-5.6\":[\"copilot/gpt-5.6*\",\"openai/gpt-5.6*\"],\"grok\":[\"copilot/*grok*\",\"openai/*grok*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"image-generation\":[\"copilot/gpt-image*\",\"openai/gpt-image*\",\"openai/chatgpt-image*\",\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"google/imagen*\"],\"kimi\":[\"copilot/kimi*\",\"openai/kimi*\"],\"kiwi\":[\"copilot/kiwi*\",\"openai/kiwi*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"lyria\":[\"google/lyria*\",\"gemini/lyria*\",\"copilot/lyria*\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mai-code-1-flash-picker\":[\"copilot/MAI-Code-1-Flash-picker*\",\"copilot/mai-code-1-flash-picker*\",\"openai/MAI-Code-1-Flash-picker*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"raptor-mini\":[\"copilot/raptor*\",\"openai/raptor*\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-5*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\",\"anthropic/*sonnet-5*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"veo\":[\"google/veo*\",\"gemini/veo*\"],\"vision\":[\"copilot/gemini-*image*\",\"google/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.28.14,squid=sha256:2ce8df3abf3e9b76e9c0cf5863da41f1ab3f89b20ad14b988806ab89e7bf2cd5,agent=sha256:f7df036c86575527b61f3f7df91c4412349a12b2a74988d929eafa2999230c98,api-proxy=sha256:6f95e2234dd9bd6333a8ff28ccea7ecf0204acd4a09108723844dbd2bf6268c5,cli-proxy=sha256:3a379c5e96e29499c815e9dd2a71334d01c326a9b73991c76544fda9cae35c34\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
|
||
cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json
|
||
export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json"
|
||
GH_AW_DOCKER_HOST=""
|
||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||
GH_AW_DOCKER_HOST="${DOCKER_HOST}"
|
||
fi
|
||
if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then
|
||
_GH_AW_CHROOT_JSON=$(jq -c --arg src "${RUNNER_TEMP}/gh-aw" --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home "${RUNNER_TEMP}/gh-aw/home" '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; }
|
||
printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
|
||
fi
|
||
GH_AW_TOOL_CACHE_MOUNT=""
|
||
GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"
|
||
if [ -d "$GH_AW_TOOL_CACHE" ]; then
|
||
if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then
|
||
GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro"
|
||
fi
|
||
fi
|
||
# shellcheck disable=SC1003,SC2016,SC2086
|
||
awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} --env-all --exclude-env ACTIONS_ID_TOKEN_REQUEST_TOKEN --exclude-env ACTIONS_ID_TOKEN_REQUEST_URL --exclude-env COPILOT_GITHUB_TOKEN --mount /tmp/gh-aw:/tmp/gh-aw:rw --mount /tmp/gh-aw/threat-detection:/tmp/gh-aw/threat-detection:rw --log-level info --skip-pull \
|
||
-- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; GH_AW_TOOL_BINS="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')"; GH_AW_TOOL_BINS="${GH_AW_TOOL_BINS%:}"; export PATH="$PATH${GH_AW_TOOL_BINS:+:}$GH_AW_TOOL_BINS"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true; [ -n "$ERLANG_HOME" ] && export PATH="$ERLANG_HOME/bin:$PATH" || true && threat-detect --engine copilot --output /tmp/gh-aw/threat-detection/detection_result.json /tmp/gh-aw/threat-detection' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
|
||
- name: Render detection log
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'render_detection_log.cjs'));
|
||
await main();
|
||
- name: Copy detection firewall logs
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
continue-on-error: true
|
||
run: |
|
||
mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall
|
||
if [ -d /tmp/gh-aw/sandbox/firewall/logs ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/logs && cp -r /tmp/gh-aw/sandbox/firewall/logs/. /tmp/gh-aw/threat-detection/sandbox/firewall/logs/; fi
|
||
if [ -d /tmp/gh-aw/sandbox/firewall/audit ]; then mkdir -p /tmp/gh-aw/threat-detection/sandbox/firewall/audit && cp -r /tmp/gh-aw/sandbox/firewall/audit/. /tmp/gh-aw/threat-detection/sandbox/firewall/audit/; fi
|
||
- name: Upload threat detection artifact
|
||
if: always() && steps.detection_guard.outputs.run_detection == 'true'
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: detection
|
||
path: |
|
||
/tmp/gh-aw/threat-detection/detection_result.json
|
||
/tmp/gh-aw/threat-detection/sandbox/firewall/logs/
|
||
/tmp/gh-aw/threat-detection/sandbox/firewall/audit/
|
||
if-no-files-found: ignore
|
||
- name: Parse threat detection token usage for step summary
|
||
id: parse_detection_token_usage
|
||
if: always()
|
||
continue-on-error: true
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage
|
||
with:
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'parse_token_usage.cjs'));
|
||
await main();
|
||
- name: Conclude threat detection
|
||
id: detection_conclusion
|
||
if: always()
|
||
continue-on-error: true
|
||
env:
|
||
RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }}
|
||
DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }}
|
||
GH_AW_DETECTION_CONTINUE_ON_ERROR: "true"
|
||
run: |
|
||
bash "${RUNNER_TEMP}/gh-aw/actions/conclude_threat_detection.sh" /tmp/gh-aw/threat-detection/detection_result.json
|
||
|
||
pat_pool:
|
||
needs: pre_activation
|
||
runs-on: ubuntu-slim
|
||
environment: copilot-pat-pool
|
||
outputs:
|
||
pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }}
|
||
steps:
|
||
- name: Configure GH_HOST for enterprise compatibility
|
||
id: ghes-host-config
|
||
shell: bash
|
||
run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||
GH_HOST="${GITHUB_SERVER_URL#https://}"
|
||
GH_HOST="${GH_HOST#http://}"
|
||
echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
|
||
- name: Select Copilot token from pool
|
||
id: select-pat-number
|
||
run: |
|
||
# Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9.
|
||
PAT_NUMBERS=()
|
||
POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖)
|
||
|
||
for i in $(seq 0 9); do
|
||
var="COPILOT_PAT_${i}"
|
||
val="${!var}"
|
||
if [ -n "$val" ]; then
|
||
PAT_NUMBERS+=(${i})
|
||
POOL_INDICATORS[${i}]="🟪"
|
||
fi
|
||
done
|
||
|
||
# If none of the entries in the pool have values, emit a warning
|
||
# and do not set an output value. The consumer can fall back to
|
||
# using COPILOT_GITHUB_TOKEN.
|
||
if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then
|
||
warning_message="::warning::None of the PAT pool entries had values "
|
||
warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)"
|
||
echo "$warning_message"
|
||
exit 0
|
||
fi
|
||
|
||
# Select a random index using the seed if specified
|
||
if [ -n "$RANDOM_SEED" ]; then
|
||
RANDOM=$RANDOM_SEED
|
||
fi
|
||
|
||
PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} ))
|
||
PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}"
|
||
POOL_INDICATORS[${PAT_NUMBER}]="✅"
|
||
|
||
echo "Pool size: ${#PAT_NUMBERS[@]}"
|
||
echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})"
|
||
|
||
# Emit a markdown table of the pool entries to the step summary
|
||
echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY"
|
||
echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY"
|
||
(IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY"
|
||
|
||
# Set the PAT number as the output
|
||
echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT"
|
||
env:
|
||
COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }}
|
||
COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }}
|
||
COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }}
|
||
COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }}
|
||
COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }}
|
||
COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }}
|
||
COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }}
|
||
COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }}
|
||
COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }}
|
||
COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }}
|
||
RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }}
|
||
shell: bash
|
||
|
||
pre_activation:
|
||
if: (!(github.event_name == 'schedule' && github.event.repository.fork))
|
||
runs-on: ubuntu-slim
|
||
environment: copilot-pat-pool
|
||
env:
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
outputs:
|
||
activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }}
|
||
matched_command: ''
|
||
setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }}
|
||
setup-span-id: ${{ steps.setup.outputs.span-id }}
|
||
setup-trace-id: ${{ steps.setup.outputs.trace-id }}
|
||
steps:
|
||
- name: Setup Scripts
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Check team membership for workflow
|
||
id: check_membership
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_REQUIRED_ROLES: "admin,maintainer,write"
|
||
with:
|
||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'check_membership.cjs'));
|
||
await main();
|
||
|
||
publish_health_report:
|
||
needs:
|
||
- agent
|
||
- detection
|
||
if: >
|
||
(!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'publish_health_report') &&
|
||
(needs.agent.result == 'success' && needs.detection.result == 'success' && needs.detection.outputs.detection_success == 'true' &&
|
||
contains(needs.agent.outputs.output_types, 'publish_health_report'))
|
||
runs-on: ubuntu-latest
|
||
environment: copilot-pat-pool
|
||
permissions:
|
||
actions: write
|
||
contents: read
|
||
issues: write
|
||
steps:
|
||
- name: Download agent output artifact
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: "{agent,agent-output-fallback}"
|
||
merge-multiple: true
|
||
path: ${{ runner.temp }}/gh-aw/safe-jobs/
|
||
- name: Publish dashboard and dependent outputs
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
|
||
env:
|
||
EXPECTED_REPOSITORY: ${{ github.repository }}
|
||
GH_AW_AGENT_OUTPUT: ${{ runner.temp }}/gh-aw/safe-jobs/agent_output.json
|
||
with:
|
||
script: |
|
||
const fs = require("fs");
|
||
|
||
const outputPath = process.env.GH_AW_AGENT_OUTPUT;
|
||
if (!outputPath) {
|
||
core.setFailed("GH_AW_AGENT_OUTPUT is not set");
|
||
return;
|
||
}
|
||
|
||
const output = JSON.parse(fs.readFileSync(outputPath, "utf8"));
|
||
const allItems = Array.isArray(output.items) ? output.items : [];
|
||
const items = allItems.filter(
|
||
item => item.type === "publish_health_report"
|
||
);
|
||
if (allItems.length !== 1 || items.length !== 1) {
|
||
core.setFailed(
|
||
`Expected publish_health_report as the only output item, got ${allItems.length} total`
|
||
);
|
||
return;
|
||
}
|
||
|
||
const item = items[0];
|
||
const stateToken = "DEVOPS_HEALTH_STATE_SLOT_V1";
|
||
const rowsToken = "DEVOPS_HEALTH_INVESTIGATION_ROWS_SLOT_V1";
|
||
const countToken = (text, token) => text.split(token).length - 1;
|
||
if (
|
||
typeof item.body !== "string" ||
|
||
!item.body.startsWith("# 🏥 Daily Health Check — ") ||
|
||
countToken(item.body, stateToken) !== 1 ||
|
||
countToken(item.body, rowsToken) !== 1 ||
|
||
item.body.includes("<!-- devops-health-state:v1") ||
|
||
item.body.includes("<!-- devops-health-investigation-results:")
|
||
) {
|
||
core.setFailed("Dashboard body is missing required publication placeholders");
|
||
return;
|
||
}
|
||
const requiredSections = [
|
||
"## 🆕 New Findings (",
|
||
"## 🔍 Investigation Results",
|
||
rowsToken,
|
||
"## ✅ Resolved Since Yesterday (",
|
||
"## 📌 Existing Findings (",
|
||
"## 📊 Trends (7-day)",
|
||
stateToken,
|
||
];
|
||
const sectionPositions = requiredSections.map(section =>
|
||
item.body.indexOf(section)
|
||
);
|
||
if (
|
||
requiredSections.some(
|
||
section => countToken(item.body, section) !== 1
|
||
) ||
|
||
sectionPositions.some(
|
||
(position, index) =>
|
||
position < 0 ||
|
||
(index > 0 && position <= sectionPositions[index - 1])
|
||
)
|
||
) {
|
||
core.setFailed("Dashboard body sections are missing, duplicated, or out of order");
|
||
return;
|
||
}
|
||
if (
|
||
typeof item.comment_body !== "string" ||
|
||
item.comment_body.length > 65000 ||
|
||
!item.comment_body.startsWith("## 📋 Health Check — ")
|
||
) {
|
||
core.setFailed("Audit comment is missing, oversized, or has the wrong heading");
|
||
return;
|
||
}
|
||
|
||
const [owner, repo] = process.env.EXPECTED_REPOSITORY.split("/");
|
||
const allowedTypes = new Set(["pipeline", "infra", "resource"]);
|
||
const allowedSeverities = new Set(["critical", "warning", "info"]);
|
||
const dashboard = await github.rest.issues.get({
|
||
owner,
|
||
repo,
|
||
issue_number: 695,
|
||
});
|
||
const repository = await github.rest.repos.get({ owner, repo });
|
||
const defaultBranch = repository.data.default_branch;
|
||
const labels = dashboard.data.labels.map(label =>
|
||
typeof label === "string" ? label : label.name
|
||
);
|
||
if (
|
||
dashboard.data.state !== "open" ||
|
||
dashboard.data.title !== "🏥 Repository Health Dashboard" ||
|
||
!labels.includes("devops-health")
|
||
) {
|
||
core.setFailed("Issue 695 failed canonical dashboard validation");
|
||
return;
|
||
}
|
||
if (typeof defaultBranch !== "string" || defaultBranch.length === 0) {
|
||
core.setFailed("Repository default branch is unavailable");
|
||
return;
|
||
}
|
||
const exactKeys = (value, keys) =>
|
||
value !== null &&
|
||
typeof value === "object" &&
|
||
!Array.isArray(value) &&
|
||
JSON.stringify(Object.keys(value).sort()) ===
|
||
JSON.stringify([...keys].sort());
|
||
const validDate = value => {
|
||
if (
|
||
typeof value !== "string" ||
|
||
!/^\d{4}-\d{2}-\d{2}$/.test(value)
|
||
) {
|
||
return false;
|
||
}
|
||
const parsed = new Date(`${value}T00:00:00.000Z`);
|
||
return (
|
||
!Number.isNaN(parsed.valueOf()) &&
|
||
parsed.toISOString().slice(0, 10) === value
|
||
);
|
||
};
|
||
const validCount = value =>
|
||
typeof value === "number" &&
|
||
Number.isFinite(value) &&
|
||
value >= 0;
|
||
const validRepositoryUrl = value => {
|
||
if (
|
||
typeof value !== "string" ||
|
||
value.length > 500 ||
|
||
/[\s()[\]|<>\\]/.test(value)
|
||
) {
|
||
return false;
|
||
}
|
||
try {
|
||
const url = new URL(value);
|
||
return (
|
||
url.protocol === "https:" &&
|
||
url.hostname === "github.com" &&
|
||
url.username === "" &&
|
||
url.password === "" &&
|
||
url.port === "" &&
|
||
(
|
||
url.pathname === `/${owner}/${repo}` ||
|
||
url.pathname.startsWith(`/${owner}/${repo}/`)
|
||
)
|
||
);
|
||
} catch {
|
||
return false;
|
||
}
|
||
};
|
||
const validIssueCommentUrl = value => {
|
||
if (!validRepositoryUrl(value)) {
|
||
return false;
|
||
}
|
||
const url = new URL(value);
|
||
return (
|
||
url.pathname === `/${owner}/${repo}/issues/695` &&
|
||
url.search === "" &&
|
||
/^#issuecomment-\d+$/.test(url.hash)
|
||
);
|
||
};
|
||
const validateCompletedComment = async row => {
|
||
const url = new URL(row.result_url);
|
||
const commentId = Number(
|
||
url.hash.slice("#issuecomment-".length)
|
||
);
|
||
if (!Number.isSafeInteger(commentId) || commentId <= 0) {
|
||
throw new Error("A completed investigation row has an invalid comment ID");
|
||
}
|
||
const response = await github.rest.issues.getComment({
|
||
owner,
|
||
repo,
|
||
comment_id: commentId,
|
||
});
|
||
const comment = response.data;
|
||
const commentBody = comment.body || "";
|
||
const lines = commentBody.split(/\r?\n/);
|
||
const findingLine = `**Finding ID:** \`${row.fingerprint}\``;
|
||
const correlationLine = `**Correlation:** ${row.correlation_id}`;
|
||
const summaryLine =
|
||
`**Executive Summary:** ${row.result_summary}`;
|
||
const runFooterPattern = new RegExp(
|
||
`^<sub>🔍 \\[Investigation Run #\\d+\\]\\(` +
|
||
`https://github\\.com/${owner}/${repo}/actions/runs/(\\d+)\\)` +
|
||
` · Dispatched by health check · ${row.correlation_id}</sub>$`
|
||
);
|
||
const runFooterLines = lines.filter(line =>
|
||
line.startsWith("<sub>🔍 [Investigation Run #")
|
||
);
|
||
const runFooterMatch =
|
||
runFooterLines.length === 1 &&
|
||
runFooterPattern.exec(runFooterLines[0]);
|
||
if (
|
||
comment.user?.login !== "github-actions[bot]" ||
|
||
comment.issue_url !==
|
||
`https://api.github.com/repos/${owner}/${repo}/issues/695` ||
|
||
comment.html_url !== row.result_url ||
|
||
!commentBody.startsWith("## 🔍 Investigation:") ||
|
||
lines.filter(line => line.startsWith("**Finding ID:**")).length !== 1 ||
|
||
!lines.includes(findingLine) ||
|
||
lines.filter(line => line.startsWith("**Correlation:**")).length !== 1 ||
|
||
!lines.includes(correlationLine) ||
|
||
lines.filter(
|
||
line => line.startsWith("**Executive Summary:**")
|
||
).length !== 1 ||
|
||
!lines.includes(summaryLine) ||
|
||
!runFooterMatch
|
||
) {
|
||
throw new Error(
|
||
"A completed investigation row does not match its trusted comment"
|
||
);
|
||
}
|
||
const run = await github.rest.actions.getWorkflowRun({
|
||
owner,
|
||
repo,
|
||
run_id: Number(runFooterMatch[1]),
|
||
});
|
||
if (
|
||
run.data.event !== "workflow_dispatch" ||
|
||
run.data.conclusion !== "success" ||
|
||
run.data.display_title !==
|
||
`DevOps Health Investigation — ${row.correlation_id}` ||
|
||
run.data.path?.split("@")[0] !==
|
||
".github/workflows/devops-health-investigate.lock.yml" ||
|
||
run.data.head_repository?.full_name !== `${owner}/${repo}`
|
||
) {
|
||
throw new Error(
|
||
"A completed investigation row does not match its trusted workflow run"
|
||
);
|
||
}
|
||
};
|
||
const validResourceUrlForType = (value, findingType) => {
|
||
if (!validRepositoryUrl(value)) {
|
||
return false;
|
||
}
|
||
const url = new URL(value);
|
||
if (url.search !== "") {
|
||
return false;
|
||
}
|
||
const root = `/${owner}/${repo}`;
|
||
if (findingType === "pipeline") {
|
||
return (
|
||
new RegExp(`^${root}/actions/runs/\\d+$`).test(url.pathname) &&
|
||
url.hash === ""
|
||
);
|
||
}
|
||
return (
|
||
url.pathname === root ||
|
||
new RegExp(
|
||
`^${root}/(actions/runs/\\d+|commit/[0-9a-fA-F]+|pull/\\d+|issues/\\d+|blob/.+|tree/.+)$`
|
||
).test(url.pathname)
|
||
);
|
||
};
|
||
const validFingerprint = value => {
|
||
if (
|
||
typeof value !== "string" ||
|
||
value.length > 300 ||
|
||
/[\r\n]/.test(value)
|
||
) {
|
||
return false;
|
||
}
|
||
const component = "[a-z0-9][a-z0-9._/()=-]*";
|
||
return (
|
||
/^pipeline:evaluation:failure-rate:(critical|warning)$/.test(value) ||
|
||
/^pipeline:evaluation:schedule-cancellation:(critical|warning)$/.test(value) ||
|
||
new RegExp(`^pipeline:${component}:${component}:timeout$`).test(value) ||
|
||
new RegExp(
|
||
`^pipeline:${component}:${component}:${component}:${component}$`
|
||
).test(value) ||
|
||
/^infra:(no-codeowners|no-dependabot|relaxed-skill-validation|verdict-warn-only|pages-deployment-failed)$/.test(value) ||
|
||
new RegExp(`^infra:unpinned-action:${component}$`).test(value) ||
|
||
new RegExp(
|
||
`^infra:orphan-skill:${component}:${component}$`
|
||
).test(value) ||
|
||
new RegExp(`^infra:orphan-plugin:${component}$`).test(value) ||
|
||
/^resource:eval-duration:(critical|warning)$/.test(value) ||
|
||
value === "resource:cost-increase"
|
||
);
|
||
};
|
||
const expectedSeverityForFingerprint = fingerprint => {
|
||
if (fingerprint.startsWith("pipeline:copilot-code-review")) {
|
||
return "info";
|
||
}
|
||
if (
|
||
/^pipeline:evaluation:(failure-rate|schedule-cancellation):(critical|warning)$/.test(
|
||
fingerprint
|
||
)
|
||
) {
|
||
return fingerprint.endsWith(":critical")
|
||
? "critical"
|
||
: "warning";
|
||
}
|
||
if (/^pipeline:[^:]+:[^:]+:timeout$/.test(fingerprint)) {
|
||
return "warning";
|
||
}
|
||
if (/^pipeline:evaluation:[^:]+:[^:]+:[^:]+$/.test(fingerprint)) {
|
||
return "critical";
|
||
}
|
||
if (/^pipeline:[^:]+:[^:]+:[^:]+:[^:]+$/.test(fingerprint)) {
|
||
return "warning";
|
||
}
|
||
if (
|
||
fingerprint === "infra:verdict-warn-only" ||
|
||
fingerprint.startsWith("infra:unpinned-action:")
|
||
) {
|
||
return "info";
|
||
}
|
||
if (fingerprint === "infra:pages-deployment-failed") {
|
||
return "critical";
|
||
}
|
||
if (fingerprint.startsWith("infra:")) {
|
||
return "warning";
|
||
}
|
||
if (/^resource:eval-duration:(critical|warning)$/.test(fingerprint)) {
|
||
return fingerprint.endsWith(":critical")
|
||
? "critical"
|
||
: "warning";
|
||
}
|
||
if (fingerprint === "resource:cost-increase") {
|
||
return "warning";
|
||
}
|
||
return null;
|
||
};
|
||
const validNumericObject = value =>
|
||
value !== null &&
|
||
typeof value === "object" &&
|
||
!Array.isArray(value) &&
|
||
Object.keys(value).length <= 20 &&
|
||
Object.values(value).every(validCount);
|
||
const parseFencedJson = (value, name, maxLength) => {
|
||
if (typeof value !== "string" || value.length > maxLength) {
|
||
throw new Error(`${name} is missing or oversized`);
|
||
}
|
||
const match = /^```json\r?\n([\s\S]*)\r?\n```$/.exec(value);
|
||
if (!match) {
|
||
throw new Error(`${name} must be one exact fenced JSON block`);
|
||
}
|
||
return JSON.parse(match[1]);
|
||
};
|
||
|
||
const validateState = (candidate, source) => {
|
||
if (
|
||
!exactKeys(candidate, ["active_findings", "history"]) ||
|
||
!Array.isArray(candidate.active_findings) ||
|
||
candidate.active_findings.length > 100 ||
|
||
!Array.isArray(candidate.history) ||
|
||
candidate.history.length > 14
|
||
) {
|
||
throw new Error(`${source} has an invalid top-level schema`);
|
||
}
|
||
const findings = new Map();
|
||
for (const finding of candidate.active_findings) {
|
||
if (
|
||
!exactKeys(finding, [
|
||
"category",
|
||
"fingerprint",
|
||
"first_seen",
|
||
"occurrences",
|
||
"severity",
|
||
"title",
|
||
"url",
|
||
]) ||
|
||
!validFingerprint(finding.fingerprint) ||
|
||
!allowedTypes.has(finding.category) ||
|
||
!finding.fingerprint.startsWith(`${finding.category}:`) ||
|
||
!allowedSeverities.has(finding.severity) ||
|
||
finding.severity !==
|
||
expectedSeverityForFingerprint(finding.fingerprint) ||
|
||
typeof finding.title !== "string" ||
|
||
finding.title.length === 0 ||
|
||
finding.title.length > 200 ||
|
||
!validRepositoryUrl(finding.url) ||
|
||
!validDate(finding.first_seen) ||
|
||
!validCount(finding.occurrences) ||
|
||
findings.has(finding.fingerprint)
|
||
) {
|
||
throw new Error(`${source} contains an invalid active finding`);
|
||
}
|
||
findings.set(finding.fingerprint, finding);
|
||
}
|
||
for (const history of candidate.history) {
|
||
if (
|
||
!exactKeys(history, [
|
||
"by_severity",
|
||
"date",
|
||
"existing_count",
|
||
"metrics",
|
||
"new_count",
|
||
"resolved_count",
|
||
]) ||
|
||
!validDate(history.date) ||
|
||
!validCount(history.new_count) ||
|
||
!validCount(history.existing_count) ||
|
||
!validCount(history.resolved_count) ||
|
||
!validNumericObject(history.by_severity) ||
|
||
!validNumericObject(history.metrics)
|
||
) {
|
||
throw new Error(`${source} contains an invalid history entry`);
|
||
}
|
||
}
|
||
return findings;
|
||
};
|
||
|
||
const currentBody = dashboard.data.body || "";
|
||
const currentStateMatches = [
|
||
...currentBody.matchAll(
|
||
/<!-- devops-health-state:v1\r?\n([\s\S]*?)\r?\n-->/g
|
||
),
|
||
];
|
||
const currentStateTokenCount =
|
||
currentBody.split("<!-- devops-health-state:v1").length - 1;
|
||
if (currentStateTokenCount > 1) {
|
||
core.setFailed("Existing dashboard state marker is duplicated");
|
||
return;
|
||
}
|
||
if (currentStateTokenCount !== currentStateMatches.length) {
|
||
core.setFailed("Existing dashboard state marker is malformed");
|
||
return;
|
||
}
|
||
if (currentStateMatches.length === 1) {
|
||
try {
|
||
validateState(
|
||
JSON.parse(currentStateMatches[0][1]),
|
||
"Existing dashboard state"
|
||
);
|
||
} catch (error) {
|
||
core.setFailed(error.message);
|
||
return;
|
||
}
|
||
}
|
||
|
||
const priorOutbox = new Map();
|
||
for (const line of currentBody.split(/\r?\n/)) {
|
||
const fingerprintMatch = line.match(
|
||
/#investigation-fingerprint:([^)]*)\)/
|
||
);
|
||
const correlationMatch = line.match(
|
||
/#investigation-correlation:(hc-\d{4}-\d{2}-\d{2}-\d+-\d+)\)/
|
||
);
|
||
const outboxStatus = line.includes("⏳ Dispatch pending")
|
||
? "dispatching"
|
||
: line.includes("🔄 Dispatched")
|
||
? "dispatched"
|
||
: null;
|
||
if (fingerprintMatch && correlationMatch && outboxStatus) {
|
||
try {
|
||
const fingerprint = decodeURIComponent(fingerprintMatch[1]);
|
||
if (priorOutbox.has(fingerprint)) {
|
||
core.setFailed("Dashboard contains duplicate outbox rows");
|
||
return;
|
||
}
|
||
priorOutbox.set(fingerprint, {
|
||
correlation: correlationMatch[1],
|
||
status: outboxStatus,
|
||
});
|
||
} catch {
|
||
core.setFailed("Dashboard contains an invalid outbox marker");
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
|
||
let state;
|
||
let stateFindings;
|
||
try {
|
||
state = parseFencedJson(item.state_json, "state_json", 100000);
|
||
stateFindings = validateState(state, "Dashboard state");
|
||
} catch (error) {
|
||
core.setFailed(error.message);
|
||
return;
|
||
}
|
||
|
||
let investigationRows;
|
||
try {
|
||
investigationRows = parseFencedJson(
|
||
item.investigation_rows_json,
|
||
"investigation_rows_json",
|
||
100000
|
||
);
|
||
} catch (error) {
|
||
core.setFailed(error.message);
|
||
return;
|
||
}
|
||
if (
|
||
!Array.isArray(investigationRows) ||
|
||
investigationRows.length > 100
|
||
) {
|
||
core.setFailed("investigation_rows_json must contain at most 100 rows");
|
||
return;
|
||
}
|
||
const escapeCell = value =>
|
||
value
|
||
.replace(/\\/g, "\\\\")
|
||
.replace(/\r\n|\r|\n/g, " ")
|
||
.replace(/([|[\]()`*_<>&])/g, "\\$1")
|
||
.replace(/@/g, "@");
|
||
const encodeMarker = value =>
|
||
encodeURIComponent(value).replace(
|
||
/[!'()*]/g,
|
||
character =>
|
||
`%${character.charCodeAt(0).toString(16).toUpperCase()}`
|
||
);
|
||
const seenRows = new Set();
|
||
const rowByFingerprint = new Map();
|
||
const validatedRows = [];
|
||
for (const row of investigationRows) {
|
||
if (
|
||
!exactKeys(row, [
|
||
"correlation_id",
|
||
"fingerprint",
|
||
"result_summary",
|
||
"result_url",
|
||
"status",
|
||
]) ||
|
||
!validFingerprint(row.fingerprint) ||
|
||
![
|
||
"pending",
|
||
"dispatching",
|
||
"dispatched",
|
||
"done",
|
||
"skipped",
|
||
].includes(row.status) ||
|
||
typeof row.correlation_id !== "string" ||
|
||
typeof row.result_summary !== "string" ||
|
||
row.result_summary.length > 300 ||
|
||
typeof row.result_url !== "string" ||
|
||
row.result_summary.includes(stateToken) ||
|
||
row.result_summary.includes(rowsToken) ||
|
||
row.result_url.includes(stateToken) ||
|
||
row.result_url.includes(rowsToken) ||
|
||
seenRows.has(row.fingerprint)
|
||
) {
|
||
core.setFailed("An investigation row failed schema validation");
|
||
return;
|
||
}
|
||
const finding = stateFindings.get(row.fingerprint);
|
||
if (!finding) {
|
||
core.setFailed("An investigation row is not active in persisted state");
|
||
return;
|
||
}
|
||
const validCorrelation =
|
||
/^hc-\d{4}-\d{2}-\d{2}-\d+-\d+$/.test(row.correlation_id);
|
||
if (
|
||
(
|
||
["dispatching", "done"].includes(row.status) &&
|
||
!validCorrelation
|
||
) ||
|
||
(
|
||
row.status === "dispatched" &&
|
||
row.correlation_id !== "" &&
|
||
!validCorrelation
|
||
) ||
|
||
(
|
||
!["dispatching", "dispatched", "done"].includes(row.status) &&
|
||
row.correlation_id !== ""
|
||
)
|
||
) {
|
||
core.setFailed("An investigation row has an invalid correlation");
|
||
return;
|
||
}
|
||
if (
|
||
row.status === "done" &&
|
||
(
|
||
row.result_summary.length === 0 ||
|
||
!validIssueCommentUrl(row.result_url)
|
||
)
|
||
) {
|
||
core.setFailed("A completed investigation row has an invalid result");
|
||
return;
|
||
}
|
||
if (row.status === "done") {
|
||
try {
|
||
await validateCompletedComment(row);
|
||
} catch (error) {
|
||
core.setFailed(error.message);
|
||
return;
|
||
}
|
||
}
|
||
if (
|
||
row.status !== "done" &&
|
||
(row.result_summary !== "" || row.result_url !== "")
|
||
) {
|
||
core.setFailed("An incomplete investigation row contains result data");
|
||
return;
|
||
}
|
||
seenRows.add(row.fingerprint);
|
||
rowByFingerprint.set(row.fingerprint, row);
|
||
validatedRows.push({ finding, row });
|
||
}
|
||
for (const [fingerprint, prior] of priorOutbox) {
|
||
if (!stateFindings.has(fingerprint)) {
|
||
continue;
|
||
}
|
||
const row = rowByFingerprint.get(fingerprint);
|
||
const allowedStatuses = prior.status === "dispatching"
|
||
? new Set(["dispatching", "done"])
|
||
: new Set(["dispatched", "done"]);
|
||
if (
|
||
!row ||
|
||
row.correlation_id !== prior.correlation ||
|
||
!allowedStatuses.has(row.status)
|
||
) {
|
||
core.setFailed(
|
||
"An active persisted outbox row was omitted or changed"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
let dispatches;
|
||
try {
|
||
dispatches = parseFencedJson(
|
||
item.dispatches_json,
|
||
"dispatches_json",
|
||
20000
|
||
);
|
||
} catch (error) {
|
||
core.setFailed(error.message);
|
||
return;
|
||
}
|
||
if (!Array.isArray(dispatches) || dispatches.length > 2) {
|
||
core.setFailed("dispatches_json must contain an array of at most two items");
|
||
return;
|
||
}
|
||
|
||
const correlations = new Set();
|
||
const dispatchedFindings = new Set();
|
||
for (const dispatch of dispatches) {
|
||
const keys = Object.keys(dispatch).sort();
|
||
const expectedKeys = [
|
||
"correlation_id",
|
||
"finding_id",
|
||
"finding_severity",
|
||
"finding_title",
|
||
"finding_type",
|
||
"health_issue_number",
|
||
"resource_url",
|
||
];
|
||
if (JSON.stringify(keys) !== JSON.stringify(expectedKeys)) {
|
||
core.setFailed("A dispatch item has unexpected or missing fields");
|
||
return;
|
||
}
|
||
if (
|
||
!allowedTypes.has(dispatch.finding_type) ||
|
||
!validFingerprint(dispatch.finding_id) ||
|
||
!dispatch.finding_id.startsWith(`${dispatch.finding_type}:`) ||
|
||
!allowedSeverities.has(dispatch.finding_severity) ||
|
||
dispatch.health_issue_number !== "695" ||
|
||
typeof dispatch.finding_title !== "string" ||
|
||
dispatch.finding_title.length === 0 ||
|
||
dispatch.finding_title.length > 200 ||
|
||
typeof dispatch.correlation_id !== "string" ||
|
||
!(
|
||
new RegExp(
|
||
`^hc-\\d{4}-\\d{2}-\\d{2}-${context.runId}-\\d+$`
|
||
).test(dispatch.correlation_id) ||
|
||
priorOutbox.get(dispatch.finding_id)?.correlation ===
|
||
dispatch.correlation_id
|
||
) ||
|
||
correlations.has(dispatch.correlation_id) ||
|
||
dispatchedFindings.has(dispatch.finding_id) ||
|
||
!validResourceUrlForType(
|
||
dispatch.resource_url,
|
||
dispatch.finding_type
|
||
)
|
||
) {
|
||
core.setFailed("A dispatch item failed field validation");
|
||
return;
|
||
}
|
||
const persistedFinding = stateFindings.get(dispatch.finding_id);
|
||
if (
|
||
!persistedFinding ||
|
||
persistedFinding.category !== dispatch.finding_type ||
|
||
persistedFinding.severity !== dispatch.finding_severity ||
|
||
persistedFinding.title !== dispatch.finding_title ||
|
||
persistedFinding.url !== dispatch.resource_url
|
||
) {
|
||
core.setFailed("A dispatch item does not match persisted dashboard state");
|
||
return;
|
||
}
|
||
correlations.add(dispatch.correlation_id);
|
||
dispatchedFindings.add(dispatch.finding_id);
|
||
}
|
||
for (const findingId of dispatchedFindings) {
|
||
const row = rowByFingerprint.get(findingId);
|
||
const dispatch = dispatches.find(
|
||
candidate => candidate.finding_id === findingId
|
||
);
|
||
if (
|
||
row?.status !== "dispatching" ||
|
||
row.correlation_id !== dispatch.correlation_id
|
||
) {
|
||
core.setFailed(
|
||
"A dispatch item lacks a matching dispatching outbox row"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
|
||
const renderRows = finalizeDispatches =>
|
||
validatedRows.map(({ finding, row }) => {
|
||
const effectiveStatus =
|
||
finalizeDispatches &&
|
||
row.status === "dispatching" &&
|
||
dispatchedFindings.has(row.fingerprint)
|
||
? "dispatched"
|
||
: row.status;
|
||
const severityEmoji = {
|
||
critical: "🔴",
|
||
warning: "🟡",
|
||
info: "🔵",
|
||
}[finding.severity];
|
||
const statusText = {
|
||
pending: "⏳ Pending — dispatch budget reached",
|
||
dispatching: "⏳ Dispatch pending",
|
||
dispatched: "🔄 Dispatched",
|
||
done: "✅ Done",
|
||
skipped: "⏳ Skipped",
|
||
}[effectiveStatus];
|
||
let resultText = "Investigation not dispatched";
|
||
if (effectiveStatus === "pending") {
|
||
resultText = "Awaiting a later dispatch slot";
|
||
} else if (effectiveStatus === "dispatching") {
|
||
resultText = "Dispatch will be retried or reconciled";
|
||
} else if (effectiveStatus === "dispatched") {
|
||
resultText =
|
||
`[⏳ Investigation dispatched — results arriving shortly...](${finding.url})`;
|
||
} else if (effectiveStatus === "done") {
|
||
resultText =
|
||
`[${escapeCell(row.result_summary)}](${row.result_url})`;
|
||
}
|
||
const correlationMarker = row.correlation_id
|
||
? ` [](https://github.com/${owner}/${repo}/issues/695` +
|
||
`#investigation-correlation:${row.correlation_id})`
|
||
: "";
|
||
return (
|
||
`| [](https://github.com/${owner}/${repo}/issues/695` +
|
||
`#investigation-fingerprint:${encodeMarker(finding.fingerprint)})` +
|
||
`${correlationMarker} ${escapeCell(finding.title)} | ` +
|
||
`${severityEmoji} ${finding.severity} | ${statusText} | ` +
|
||
`${finding.first_seen} | ${resultText} |`
|
||
);
|
||
}).join("\n");
|
||
|
||
const serializedState = JSON.stringify(state);
|
||
if (
|
||
serializedState.includes("<!--") ||
|
||
serializedState.includes("-->") ||
|
||
serializedState.includes(stateToken) ||
|
||
serializedState.includes(rowsToken)
|
||
) {
|
||
core.setFailed(
|
||
"Dashboard state contains a reserved delimiter or publication sentinel"
|
||
);
|
||
return;
|
||
}
|
||
const stateMarker =
|
||
`<!-- devops-health-state:v1\n${serializedState}\n-->`;
|
||
const outboxBody = item.body
|
||
.replace(stateToken, () => stateMarker)
|
||
.replace(rowsToken, () => renderRows(false));
|
||
const publishedBody = item.body
|
||
.replace(stateToken, () => stateMarker)
|
||
.replace(rowsToken, () => renderRows(true));
|
||
for (const renderedBody of [outboxBody, publishedBody]) {
|
||
const renderedStateMatches = [
|
||
...renderedBody.matchAll(
|
||
/<!-- devops-health-state:v1\r?\n([\s\S]*?)\r?\n-->/g
|
||
),
|
||
];
|
||
if (
|
||
renderedStateMatches.length !== 1 ||
|
||
countToken(renderedBody, "<!-- devops-health-state:v1") !== 1 ||
|
||
renderedBody.includes(stateToken) ||
|
||
renderedBody.includes(rowsToken)
|
||
) {
|
||
core.setFailed(
|
||
"Rendered dashboard body has invalid publication markers"
|
||
);
|
||
return;
|
||
}
|
||
}
|
||
if (outboxBody.length > 60000 || publishedBody.length > 60000) {
|
||
core.setFailed("Rendered dashboard body exceeds 60000 characters");
|
||
return;
|
||
}
|
||
|
||
// Persistence is the prerequisite. Any failure throws and stops
|
||
// before the comment or workflow dispatch operations.
|
||
await github.rest.issues.update({
|
||
owner,
|
||
repo,
|
||
issue_number: 695,
|
||
body: outboxBody,
|
||
});
|
||
|
||
for (const dispatch of dispatches) {
|
||
const expectedRunName =
|
||
`DevOps Health Investigation — ${dispatch.correlation_id}`;
|
||
const runs = await github.rest.actions.listWorkflowRuns({
|
||
owner,
|
||
repo,
|
||
workflow_id: "devops-health-investigate.lock.yml",
|
||
branch: defaultBranch,
|
||
event: "workflow_dispatch",
|
||
per_page: 100,
|
||
});
|
||
const alreadyDispatched = runs.data.workflow_runs.some(
|
||
run => run.display_title === expectedRunName
|
||
);
|
||
if (!alreadyDispatched) {
|
||
await github.rest.actions.createWorkflowDispatch({
|
||
owner,
|
||
repo,
|
||
workflow_id: "devops-health-investigate.lock.yml",
|
||
ref: defaultBranch,
|
||
inputs: {
|
||
...dispatch,
|
||
dry_run: "false",
|
||
},
|
||
});
|
||
}
|
||
}
|
||
|
||
await github.rest.issues.update({
|
||
owner,
|
||
repo,
|
||
issue_number: 695,
|
||
body: publishedBody,
|
||
});
|
||
|
||
const publicationMarker =
|
||
`<!-- devops-health-publication:${context.runId} -->`;
|
||
let commentExists = false;
|
||
const since = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||
.toISOString();
|
||
for (let page = 1; page <= 5 && !commentExists; page += 1) {
|
||
const comments = await github.rest.issues.listComments({
|
||
owner,
|
||
repo,
|
||
issue_number: 695,
|
||
since,
|
||
per_page: 100,
|
||
page,
|
||
});
|
||
commentExists = comments.data.some(
|
||
comment => comment.body?.includes(publicationMarker)
|
||
);
|
||
if (comments.data.length < 100) {
|
||
break;
|
||
}
|
||
}
|
||
if (!commentExists) {
|
||
await github.rest.issues.createComment({
|
||
owner,
|
||
repo,
|
||
issue_number: 695,
|
||
body: `${item.comment_body}\n\n${publicationMarker}`,
|
||
});
|
||
}
|
||
|
||
safe_outputs:
|
||
needs:
|
||
- activation
|
||
- agent
|
||
- detection
|
||
if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
|
||
runs-on: ubuntu-slim
|
||
environment: copilot-pat-pool
|
||
permissions: {}
|
||
timeout-minutes: 45
|
||
env:
|
||
GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }}
|
||
GH_AW_AIC: ${{ needs.agent.outputs.aic }}
|
||
GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }}
|
||
GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/devops-health-check"
|
||
GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }}
|
||
GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }}
|
||
GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }}
|
||
GH_AW_ENGINE_ID: "copilot"
|
||
GH_AW_ENGINE_MODEL: "${{ vars.GH_AW_MODEL_AGENT_COPILOT || vars.GH_AW_DEFAULT_MODEL_COPILOT || 'gpt-5.6-sol' }}"
|
||
GH_AW_RUNTIME_FEATURES: ${{ vars.GH_AW_RUNTIME_FEATURES }}
|
||
GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }}
|
||
GH_AW_WORKFLOW_ID: "devops-health-check"
|
||
GH_AW_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/devops-health-check.md"
|
||
outputs:
|
||
code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }}
|
||
code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }}
|
||
create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }}
|
||
create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }}
|
||
process_safe_outputs_items_applied: ${{ steps.process_safe_outputs.outputs.items_applied }}
|
||
process_safe_outputs_items_cancelled: ${{ steps.process_safe_outputs.outputs.items_cancelled }}
|
||
process_safe_outputs_items_deferred: ${{ steps.process_safe_outputs.outputs.items_deferred }}
|
||
process_safe_outputs_items_failed: ${{ steps.process_safe_outputs.outputs.items_failed }}
|
||
process_safe_outputs_items_skipped: ${{ steps.process_safe_outputs.outputs.items_skipped }}
|
||
process_safe_outputs_items_succeeded: ${{ steps.process_safe_outputs.outputs.items_succeeded }}
|
||
process_safe_outputs_items_warnings: ${{ steps.process_safe_outputs.outputs.items_warnings }}
|
||
process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }}
|
||
process_safe_outputs_status: ${{ steps.process_safe_outputs.outputs.status }}
|
||
process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }}
|
||
steps:
|
||
- name: Setup Scripts
|
||
id: setup
|
||
uses: github/gh-aw-actions/setup@5e508589e03a7757a7e05b26e834292f5445bfb6 # v0.88.7
|
||
with:
|
||
destination: ${{ runner.temp }}/gh-aw/actions
|
||
job-name: ${{ github.job }}
|
||
trace-id: ${{ needs.activation.outputs.setup-trace-id }}
|
||
parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }}
|
||
env:
|
||
GH_AW_SETUP_WORKFLOW_NAME: "DevOps Daily Health Check"
|
||
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/devops-health-check.lock.yml@${{ github.ref }}
|
||
GH_AW_INFO_VERSION: "1.0.80"
|
||
GH_AW_INFO_AWF_VERSION: "v0.28.14"
|
||
GH_AW_INFO_ENGINE_ID: "copilot"
|
||
- name: Mask OTLP telemetry headers
|
||
run: bash "${RUNNER_TEMP}/gh-aw/actions/mask_otlp_headers.sh"
|
||
- name: Download agent output artifact
|
||
id: download-agent-output
|
||
continue-on-error: true
|
||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||
with:
|
||
pattern: "{agent,agent-output-fallback}"
|
||
merge-multiple: true
|
||
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
|
||
if [ -f "/tmp/gh-aw/agent_output.json" ]; then
|
||
echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
|
||
fi
|
||
- name: Configure GH_HOST for enterprise compatibility
|
||
id: ghes-host-config
|
||
shell: bash
|
||
run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input.
|
||
# Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct
|
||
# GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.
|
||
GH_HOST="${GITHUB_SERVER_URL#https://}"
|
||
GH_HOST="${GH_HOST#http://}"
|
||
echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV"
|
||
- name: Process Safe Outputs
|
||
id: process_safe_outputs
|
||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||
env:
|
||
GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }}
|
||
GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }}
|
||
GH_AW_ALLOWED_DOMAINS: "api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com"
|
||
GITHUB_SERVER_URL: ${{ github.server_url }}
|
||
GITHUB_API_URL: ${{ github.api_url }}
|
||
GH_AW_SAFE_OUTPUT_JOBS: "{\"publish_health_report\":\"\"}"
|
||
GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"}}"
|
||
with:
|
||
github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||
script: |
|
||
const path = require('path');
|
||
const actionsDir = path.join(process.env.RUNNER_TEMP, 'gh-aw', 'actions');
|
||
const { setupGlobals } = require(path.join(actionsDir, 'setup_globals.cjs'));
|
||
setupGlobals(core, github, context, exec, io, getOctokit);
|
||
const { main } = require(path.join(actionsDir, 'process_safe_outputs.cjs'));
|
||
await main();
|
||
- name: Upload Safe Outputs Items
|
||
if: always()
|
||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||
with:
|
||
name: safe-outputs-items
|
||
path: |
|
||
/tmp/gh-aw/safe-output-items.jsonl
|
||
/tmp/gh-aw/temporary-id-map.json
|
||
/tmp/gh-aw/safe-output-errors.json
|
||
if-no-files-found: ignore
|