diff --git a/.github/workflows/mgmt-sdk-pr-review.lock.yml b/.github/workflows/mgmt-sdk-pr-review.lock.yml
index ba8508522424..50cd1886da92 100644
--- a/.github/workflows/mgmt-sdk-pr-review.lock.yml
+++ b/.github/workflows/mgmt-sdk-pr-review.lock.yml
@@ -1,6 +1,6 @@
-# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"aba9fa1bf476fa7c5496bf00373379973fb782e686f1be2d3a2dec9a6b0e4e81","body_hash":"363e4430ab13289db056ee0b74e2eeacd4185e1fd3ee6215df9a47a8b917ef83","compiler_version":"v0.87.1","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}
-# gh-aw-manifest: {"version":1,"secrets":["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":"423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3","version":"423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.28.1","digest":"sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.28.1@sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.1","digest":"sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.28.1@sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.28.1","digest":"sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.28.1@sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.9","digest":"sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.9.0","digest":"sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e","pinned_image":"ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e"}],"has_pull_request_target":true}
-# This file was automatically generated by gh-aw (v0.87.1). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
+# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"95c95f0b11dc59314d85eb05c902858fc33d6fadfa82684879056163038463dc","body_hash":"363ac254b44a9ac6da716d4ef7cc5d269384f164aff2a4896d6be1900c755843","compiler_version":"v0.85.4","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.78"}}
+# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","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":"8c7d04ebf1ece56cd381446125da3e0f6896294a","version":"v0.80.9"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44","digest":"sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44","digest":"sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44","digest":"sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.8","digest":"sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196","pinned_image":"ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196"},{"image":"ghcr.io/github/github-mcp-server:v1.8.0","digest":"sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520","pinned_image":"ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520"}],"has_pull_request_target":true}
+# This file was automatically generated by gh-aw (v0.85.4). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md
#
# ___ _ _
# / _ \ | | (_)
@@ -26,6 +26,7 @@
# Review Python management SDK pull requests against the current repository rules and report actionable findings.
#
# Secrets used:
+# - COPILOT_GITHUB_TOKEN
# - GH_AW_GITHUB_MCP_SERVER_TOKEN
# - GH_AW_GITHUB_TOKEN
# - GITHUB_TOKEN
@@ -39,15 +40,15 @@
# - 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@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3
+# - github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
#
# Container images used:
-# - ghcr.io/github/gh-aw-firewall/agent:0.28.1@sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d
-# - ghcr.io/github/gh-aw-firewall/api-proxy:0.28.1@sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c
-# - ghcr.io/github/gh-aw-firewall/squid:0.28.1@sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f
-# - ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f
+# - ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4
+# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7
+# - ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627
+# - ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8
# - ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196
-# - ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e
+# - ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520
name: "Python Management SDK PR Review"
on:
@@ -93,7 +94,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -103,8 +104,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Generate agentic run info
id: generate_aw_info
@@ -112,19 +113,18 @@ jobs:
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 || 'auto' }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AGENT_VERSION: "1.0.80"
- GH_AW_INFO_CLI_VERSION: "v0.87.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AGENT_VERSION: "1.0.78"
+ GH_AW_INFO_CLI_VERSION: "v0.85.4"
GH_AW_INFO_WORKFLOW_NAME: "Python Management SDK PR Review"
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.1"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
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:
@@ -181,6 +181,7 @@ jobs:
id: check-oauth-tokens
run: bash "${RUNNER_TEMP}/gh-aw/actions/check_oauth_tokens.sh"
env:
+ COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
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
@@ -200,8 +201,8 @@ jobs:
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"
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh"
- name: Check workflow lock file
id: check-lock-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -217,7 +218,7 @@ jobs:
- name: Check compile-agentic version
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
- GH_AW_COMPILED_VERSION: "v0.87.1"
+ GH_AW_COMPILED_VERSION: "v0.85.4"
with:
script: |
const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
@@ -239,12 +240,9 @@ jobs:
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_PROMPT: /tmp/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\"},{\"file\":\"mcp_cli_tools_with_safeoutputs_prompt.md\"},{\"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\"}]}"
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) }}
@@ -254,22 +252,63 @@ jobs:
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: "\n"
- GH_AW_PROMPT_CONTENT_0001: "\nTools: add_comment, missing_tool, missing_data, noop\n"
- GH_AW_PROMPT_CONTENT_0002: "\n"
- GH_AW_PROMPT_CONTENT_0003: "\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\n\n"
- GH_AW_PROMPT_CONTENT_0004: "\n"
- GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import .github/workflows/mgmt-sdk-pr-review.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);
+ # poutine:ignore untrusted_checkout_exec
+ run: |
+ bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh"
+ {
+ cat << 'GH_AW_PROMPT_91c0fd18a502b8d8_EOF'
+
+ GH_AW_PROMPT_91c0fd18a502b8d8_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md"
+ cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md"
+ cat << 'GH_AW_PROMPT_91c0fd18a502b8d8_EOF'
+
+ Tools: add_comment, missing_tool, missing_data, noop
+
+ GH_AW_PROMPT_91c0fd18a502b8d8_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md"
+ cat << 'GH_AW_PROMPT_91c0fd18a502b8d8_EOF'
+
+ The following GitHub context information is available for this workflow:
+ {{#if github.actor}}
+ - **actor**: __GH_AW_GITHUB_ACTOR__
+ {{/if}}
+ {{#if github.repository}}
+ - **repository**: __GH_AW_GITHUB_REPOSITORY__
+ {{/if}}
+ {{#if github.workspace}}
+ - **workspace**: __GH_AW_GITHUB_WORKSPACE__
+ {{/if}}
+ {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}}
+ - **issue-number**: #__GH_AW_EXPR_802A9F6A__
+ {{/if}}
+ {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}}
+ - **discussion-number**: #__GH_AW_EXPR_1A3A194A__
+ {{/if}}
+ {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}}
+ - **pull-request-number**: #__GH_AW_EXPR_463A214A__
+ {{/if}}
+ {{#if github.event.comment.id || github.aw.context.comment_id}}
+ - **comment-id**: __GH_AW_EXPR_FF1D34CE__
+ {{/if}}
+ {{#if github.run_id}}
+ - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__
+ {{/if}}
+
+
+ GH_AW_PROMPT_91c0fd18a502b8d8_EOF
+ cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md"
+ cat << 'GH_AW_PROMPT_91c0fd18a502b8d8_EOF'
+
+ {{#runtime-import .github/workflows/mgmt-sdk-pr-review.md}}
+ GH_AW_PROMPT_91c0fd18a502b8d8_EOF
+ } > "$GH_AW_PROMPT"
- 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_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_ENGINE_ID: "copilot"
GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
GH_AW_GITHUB_REPOSITORY: ${{ github.repository }}
@@ -282,7 +321,7 @@ jobs:
- 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_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }}
@@ -320,18 +359,14 @@ jobs:
});
- 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"
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh"
- name: Print prompt
env:
- GH_AW_PROMPT: ${{ 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/
+ GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
+ # poutine:ignore untrusted_checkout_exec
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh"
- name: Upload activation artifact
if: success()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
@@ -388,12 +423,11 @@ jobs:
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@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -402,8 +436,8 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Set runtime paths
id: set-runtime-paths
@@ -431,14 +465,22 @@ jobs:
name: Collect management SDK review context
run: "python - <<'PY'\nimport base64\nimport binascii\nimport json\nimport os\nimport re\nimport urllib.error\nimport urllib.parse\nimport urllib.request\n\n\nAPI_ROOT = os.environ.get(\"GH_API_ROOT\", \"https://api.github.com\")\nREPOSITORY = os.environ[\"GH_REPOSITORY\"]\nPR_NUMBER = int(os.environ[\"PR_NUMBER\"])\nTOKEN = os.environ[\"GH_TOKEN\"]\nPACKAGE_PATTERN = re.compile(r\"^(sdk/[^/]+/azure-mgmt-[^/]+)(?:/|$)\")\n\n\nclass GitHubApiError(RuntimeError):\n pass\n\n\ndef api_get(path):\n request = urllib.request.Request(\n f\"{API_ROOT}{path}\",\n headers={\n \"Accept\": \"application/vnd.github+json\",\n \"Authorization\": f\"Bearer {TOKEN}\",\n \"User-Agent\": \"azure-sdk-python-mgmt-review\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n )\n try:\n with urllib.request.urlopen(request) as response:\n return json.load(response)\n except urllib.error.HTTPError as error:\n detail = error.read().decode(\"utf-8\", errors=\"replace\")\n raise GitHubApiError(f\"GitHub API request failed ({error.code}) for {path}: {detail}\") from error\n except urllib.error.URLError as error:\n raise GitHubApiError(f\"GitHub API request failed for {path}: {error.reason}\") from error\n\n\ndef paged_get(path, max_items=None):\n items = []\n page = 1\n while True:\n separator = \"&\" if \"?\" in path else \"?\"\n batch = api_get(f\"{path}{separator}per_page=100&page={page}\")\n if not isinstance(batch, list):\n raise GitHubApiError(f\"GitHub API returned a non-list response for {path}\")\n items.extend(batch)\n if max_items is not None and len(items) >= max_items:\n return items[:max_items]\n if len(batch) < 100:\n return items\n page += 1\n\n\ndef read_repository_file(path, revision):\n encoded_path = urllib.parse.quote(path, safe=\"/\")\n encoded_ref = urllib.parse.quote(revision, safe=\"\")\n payload = api_get(\n f\"/repos/{REPOSITORY}/contents/{encoded_path}?ref={encoded_ref}\"\n )\n try:\n if payload.get(\"encoding\") != \"base64\":\n raise ValueError(\"content was not base64 encoded\")\n return base64.b64decode(payload[\"content\"]).decode(\"utf-8\")\n except (binascii.Error, KeyError, TypeError, ValueError, UnicodeDecodeError) as error:\n raise GitHubApiError(f\"Could not read {path} at {revision}: {error}\") from error\n\n\ndef extract_management_review_rules(instructions):\n lines = instructions.splitlines()\n heading = \"## MGMT SDK Code Review Rules\"\n try:\n start = lines.index(heading)\n except ValueError as error:\n raise GitHubApiError(f\"{heading} was not found in .github/copilot-instructions.md\") from error\n end = next(\n (index for index in range(start + 1, len(lines)) if lines[index].startswith(\"## \")),\n len(lines),\n )\n return \"\\n\".join(lines[start:end]).strip()\n\n\ndef read_api_version(package_path, revision):\n metadata_path = f\"{package_path}/_metadata.json\"\n encoded_path = urllib.parse.quote(metadata_path, safe=\"/\")\n encoded_ref = urllib.parse.quote(revision, safe=\"\")\n try:\n payload = api_get(\n f\"/repos/{REPOSITORY}/contents/{encoded_path}?ref={encoded_ref}\"\n )\n except GitHubApiError as error:\n return None, str(error)\n\n try:\n if payload.get(\"encoding\") != \"base64\":\n raise ValueError(\"content was not base64 encoded\")\n content = base64.b64decode(payload[\"content\"]).decode(\"utf-8\")\n api_version = json.loads(content)[\"apiVersion\"]\n if not isinstance(api_version, str) or not api_version:\n raise ValueError(\"apiVersion was missing or was not a non-empty string\")\n return api_version, None\n except (binascii.Error, KeyError, TypeError, ValueError, UnicodeDecodeError) as error:\n return None, f\"Could not read apiVersion from {metadata_path} at {revision}: {error}\"\n\n\nrepository = api_get(f\"/repos/{REPOSITORY}\")\ndefault_branch = repository.get(\"default_branch\")\nif not isinstance(default_branch, str) or not default_branch:\n raise GitHubApiError(\"Repository metadata did not contain a default branch\")\ninstructions = read_repository_file(\".github/copilot-instructions.md\", default_branch)\nmanagement_review_rules = extract_management_review_rules(instructions)\n\npull_request = api_get(f\"/repos/{REPOSITORY}/pulls/{PR_NUMBER}\")\nexpected_changed_files = pull_request.get(\"changed_files\")\nif not isinstance(expected_changed_files, int) or expected_changed_files < 0:\n raise GitHubApiError(\"Pull request metadata did not contain a valid changed_files count\")\nlatest_revision = pull_request.get(\"head\", {}).get(\"sha\")\nif not isinstance(latest_revision, str) or not latest_revision:\n raise GitHubApiError(\"Pull request metadata did not contain a valid head SHA\")\n\nchanged_files = paged_get(\n f\"/repos/{REPOSITORY}/pulls/{PR_NUMBER}/files\",\n max_items=3000,\n)\nreturned_changed_files = len(changed_files)\npackage_discovery_complete = returned_changed_files == expected_changed_files\npackage_discovery_error = None\nif not package_discovery_complete:\n package_discovery_error = (\n \"Management package discovery is incomplete: pull request metadata reports \"\n f\"{expected_changed_files} changed files, but the GitHub API returned \"\n f\"{returned_changed_files}. GitHub limits pull request file responses to 3,000 files.\"\n )\n\npackage_paths = sorted(\n {\n match.group(1)\n for item in changed_files\n for field in (\"filename\", \"previous_filename\")\n for path in [item.get(field)]\n if isinstance(path, str)\n for match in [PACKAGE_PATTERN.match(path)]\n if match\n }\n)\n\ncommits = paged_get(\n f\"/repos/{REPOSITORY}/pulls/{PR_NUMBER}/commits\",\n max_items=250,\n)\ncommit_shas = [item.get(\"sha\") for item in commits if isinstance(item.get(\"sha\"), str)]\nif not commit_shas:\n raise GitHubApiError(\"Pull request metadata returned an empty commit list\")\n\nfirst_revision = commit_shas[0]\ndrift_results = []\nfor package_path in package_paths:\n first_api_version, first_error = read_api_version(package_path, first_revision)\n latest_api_version, latest_error = read_api_version(package_path, latest_revision)\n errors = [error for error in (first_error, latest_error) if error]\n if errors:\n status = \"unverified\"\n elif first_api_version == latest_api_version:\n status = \"unchanged\"\n else:\n status = \"changed\"\n drift_results.append(\n {\n \"packagePath\": package_path,\n \"metadataPath\": f\"{package_path}/_metadata.json\",\n \"status\": status,\n \"firstRevision\": first_revision,\n \"firstApiVersion\": first_api_version,\n \"latestRevision\": latest_revision,\n \"latestApiVersion\": latest_api_version,\n \"error\": \"; \".join(errors) if errors else None,\n }\n )\n\ncontext = {\n \"repository\": REPOSITORY,\n \"pullRequestNumber\": PR_NUMBER,\n \"rulesSource\": f\".github/copilot-instructions.md@{default_branch}\",\n \"mgmtSdkCodeReviewRules\": management_review_rules,\n \"packageDiscovery\": {\n \"status\": \"complete\" if package_discovery_complete else \"unverified\",\n \"expectedChangedFiles\": expected_changed_files,\n \"returnedChangedFiles\": returned_changed_files,\n \"error\": package_discovery_error,\n },\n \"affectedPackages\": package_paths,\n \"changedFiles\": [\n {\n \"filename\": item.get(\"filename\"),\n \"previousFilename\": item.get(\"previous_filename\"),\n \"status\": item.get(\"status\"),\n \"additions\": item.get(\"additions\"),\n \"deletions\": item.get(\"deletions\"),\n }\n for item in changed_files\n ],\n \"firstRevision\": first_revision,\n \"latestRevision\": latest_revision,\n \"apiVersionDrift\": drift_results,\n}\nwith open(\"review-context.json\", \"w\", encoding=\"utf-8\") as output:\n json.dump(context, output, indent=2)\n output.write(\"\\n\")\nPY\n"
shell: bash
+ - env:
+ GH_REPOSITORY: ${{ github.repository }}
+ GH_TOKEN: ${{ github.token }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ TRUSTED_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ name: Collect breaking-change attribution context
+ run: "python - <<'PY'\nimport base64\nimport json\nimport os\nimport pathlib\nimport re\nimport urllib.parse\nimport urllib.request\n\nrepository = os.environ[\"GH_REPOSITORY\"]\nrevision = os.environ[\"TRUSTED_BASE_SHA\"]\nif not re.fullmatch(r\"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\", repository):\n raise SystemExit(\"Invalid repository reference\")\nif not re.fullmatch(r\"[0-9a-f]{40}\", revision):\n raise SystemExit(\"Invalid trusted base revision\")\npath = \".github/workflows/scripts/mgmt_sdk_review_context.py\"\nurl = (\n f\"https://api.github.com/repos/{repository}/contents/\"\n f\"{urllib.parse.quote(path, safe='/')}?ref={revision}\"\n)\nrequest = urllib.request.Request(\n url,\n headers={\n \"Accept\": \"application/vnd.github+json\",\n \"Authorization\": f\"Bearer {os.environ['GH_TOKEN']}\",\n \"User-Agent\": \"azure-sdk-python-mgmt-review\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n },\n)\n with urllib.request.urlopen(request, timeout=30) as response:\n payload = json.load(response)\n encoded_content = re.sub(r\"\\s+\", \"\", payload[\"content\"])\n content = base64.b64decode(encoded_content, validate=True)\nif len(content) > 128 * 1024:\n raise SystemExit(\"Trusted collector exceeded the size limit\")\nscript = pathlib.Path(\"mgmt_sdk_review_context.py\")\nscript.write_bytes(content)\nPY\npython mgmt_sdk_review_context.py\n"
+ shell: bash
- 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.87.1
+ GH_AW_COMPILED_VERSION: v0.85.4
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.1 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44 --rootless
- name: Determine automatic lockdown mode for GitHub MCP Server
id: determine-automatic-lockdown
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9)
@@ -459,24 +501,15 @@ jobs:
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.1@sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d ghcr.io/github/gh-aw-firewall/api-proxy:0.28.1@sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c ghcr.io/github/gh-aw-firewall/squid:0.28.1@sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f ghcr.io/github/gh-aw-mcpg:v0.4.9@sha256:e5a1569aeaf41820fa7bdee3e94468cae448133cdbf00119ad24f5b74db1ab9f ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 ghcr.io/github/github-mcp-server:v1.9.0@sha256:881b53d6f75f69bdbc1b5b10fc2f1361717c19054143b3a8529fb5c32061a50e
- - name: Prepare Safe Outputs Directories
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627 ghcr.io/github/gh-aw-mcpg:v0.4.8@sha256:38bbea36cdb46a3c9d04d1db05e672966f5239b431a2022eb35881688e5721d8 ghcr.io/github/gh-aw-node@sha256:0d9f1fb5fd6610c0ac1f5194a38e45a8a1e81f8a390d5142d8e4e6f26a4b3196 ghcr.io/github/github-mcp-server:v1.8.0@sha256:d5a18c04b92714c309eb46a2305087e91a4dbd80420f6e462656699f95093520
+ - name: Generate Safe Outputs Config
run: |
mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs"
mkdir -p /tmp/gh-aw/safeoutputs
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
- - 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: "{\"add_comment\":{\"discussions\":false,\"footer\":false,\"hide_older_comments\":true,\"max\":1,\"target\":\"${{ github.event.pull_request.number }}\"},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"false\"},\"report_incomplete\":{}}"
- with:
- script: |
- const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/create_files.cjs');
- await main();
+ cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_83d1b3f0eab48f8c_EOF'
+ {"add_comment":{"discussions":false,"footer":false,"hide_older_comments":true,"max":1,"target":"${{ github.event.pull_request.number }}"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}}
+ GH_AW_SAFE_OUTPUTS_CONFIG_83d1b3f0eab48f8c_EOF
- name: Generate Safe Outputs Tools
env:
GH_AW_TOOLS_META_JSON: |
@@ -498,18 +531,9 @@ jobs:
"sanitize": true,
"maxLength": 65000
},
- "comment_id": {
- "optionalPositiveInteger": true
- },
"item_number": {
"issueOrPRNumber": true
},
- "pr": {
- "issueOrPRNumber": true
- },
- "pr_number": {
- "issueOrPRNumber": true
- },
"reply_to_id": {
"type": "string",
"maxLength": 256
@@ -517,16 +541,6 @@ jobs:
"repo": {
"type": "string",
"maxLength": 256
- },
- "target": {
- "type": "string",
- "enum": [
- "status"
- ]
- },
- "temporary_id": {
- "type": "string",
- "pattern": "^#?aw_[A-Za-z0-9_]{3,12}$"
}
}
},
@@ -626,11 +640,6 @@ jobs:
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"
@@ -642,23 +651,22 @@ jobs:
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 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_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_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 MCP_GATEWAY_ALLOWED_MOUNT_ROOTS -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.9'
+ 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_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_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 -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.8'
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_e6be7d378fafeec2_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs"
+ cat << GH_AW_MCP_CONFIG_771090438ccb49f9_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.9.0",
+ "container": "ghcr.io/github/github-mcp-server:v1.8.0",
"env": {
"GITHUB_FEATURES": "fields_param",
"GITHUB_HOST": "${GITHUB_SERVER_URL}",
@@ -691,8 +699,6 @@ jobs:
"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}",
- "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}",
- "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}",
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
"GITHUB_SHA": "\${GITHUB_SHA}",
"GITHUB_TOKEN": "\${GITHUB_TOKEN}",
@@ -717,7 +723,7 @@ jobs:
"startupTimeout": 120
}
}
- GH_AW_MCP_CONFIG_e6be7d378fafeec2_EOF
+ GH_AW_MCP_CONFIG_771090438ccb49f9_EOF
- name: Mount MCP servers as CLIs
id: mount-mcp-clis
continue-on-error: true
@@ -764,30 +770,18 @@ jobs:
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
+ 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"' 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}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.1/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"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.1,squid=sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f,agent=sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d,api-proxy=sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c,cli-proxy=sha256:f931e5e1e13f765605d03ef9511fc755d779a51b76581ea14586e9871506a610\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"github.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"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-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.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff\"},\"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=""
@@ -805,8 +799,8 @@ jobs:
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 --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --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"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -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 --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
+ 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_API_KEY --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"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -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 /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-tool github --allow-tool safeoutputs --allow-tool '\''shell(cat)'\'' --allow-tool '\''shell(date)'\'' --allow-tool '\''shell(echo)'\'' --allow-tool '\''shell(github:*)'\'' --allow-tool '\''shell(grep)'\'' --allow-tool '\''shell(head)'\'' --allow-tool '\''shell(ls)'\'' --allow-tool '\''shell(printf)'\'' --allow-tool '\''shell(pwd)'\'' --allow-tool '\''shell(safeoutputs:*)'\'' --allow-tool '\''shell(sort)'\'' --allow-tool '\''shell(tail)'\'' --allow-tool '\''shell(uniq)'\'' --allow-tool '\''shell(wc)'\'' --allow-tool '\''shell(yq)'\'' --allow-tool write --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
env:
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
@@ -820,7 +814,7 @@ jobs:
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: 30
- GH_AW_VERSION: v0.87.1
+ GH_AW_VERSION: v0.85.4
GITHUB_API_URL: ${{ github.api_url }}
GITHUB_AW: true
GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows
@@ -841,9 +835,6 @@ jobs:
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: 30
run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs"
- name: Copy Copilot session state files to logs
if: always()
@@ -951,18 +942,6 @@ jobs:
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
@@ -1018,7 +997,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1027,16 +1006,15 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
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
+ name: agent
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1044,9 +1022,7 @@ jobs:
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
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- name: Download Safe Outputs Items Manifest
id: download-safe-outputs-manifest
if: always()
@@ -1214,7 +1190,6 @@ jobs:
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 }}
@@ -1271,7 +1246,7 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1280,22 +1255,15 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
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
+ name: agent
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1303,9 +1271,7 @@ jobs:
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
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- name: Checkout repository for patch context
if: needs.agent.outputs.has_patch == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -1317,7 +1283,7 @@ jobs:
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.1@sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d ghcr.io/github/gh-aw-firewall/api-proxy:0.28.1@sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c ghcr.io/github/gh-aw-firewall/squid:0.28.1@sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.44@sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.44@sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7 ghcr.io/github/gh-aw-firewall/squid:0.27.44@sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627
- name: Check if detection needed
id: detection_guard
if: always()
@@ -1341,7 +1307,25 @@ jobs:
- 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"
+ mkdir -p /tmp/gh-aw/threat-detection/aw-prompts
+ rm -f /tmp/gh-aw/agent_usage.json
+ cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true
+ if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then
+ echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context."
+ fi
+ cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true
+ for f in /tmp/gh-aw/aw-*.patch; do
+ if [ -f "$f" ]; then
+ cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ fi
+ done
+ for f in /tmp/gh-aw/aw-*.bundle; do
+ if [ -f "$f" ]; then
+ cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true
+ fi
+ done
+ echo "Prepared threat detection files:"
+ ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true
- name: Setup threat detection
if: always() && steps.detection_guard.outputs.run_detection == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -1361,8 +1345,8 @@ jobs:
run: |
mkdir -p /tmp/gh-aw/threat-detection
touch /tmp/gh-aw/threat-detection/detection.log
- rm -f /tmp/gh-aw/step-summary.md
- touch /tmp/gh-aw/step-summary.md
+ rm -f /tmp/gh-aw/threat-detection/step-summary.md
+ touch /tmp/gh-aw/threat-detection/step-summary.md
- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -1372,9 +1356,9 @@ jobs:
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh"
env:
GH_HOST: github.com
- GH_AW_COMPILED_VERSION: v0.87.1
+ GH_AW_COMPILED_VERSION: v0.85.4
- name: Install AWF binary
- run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.28.1 --rootless
+ run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.44
- name: Execute GitHub Copilot CLI
if: always() && steps.detection_guard.outputs.run_detection == 'true'
continue-on-error: true
@@ -1384,29 +1368,17 @@ jobs:
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
+ 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"' EXIT
mkdir -p "$HOME/.copilot"
printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json"
export XDG_CONFIG_HOME="$HOME"
- 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/threat-detection/detection.log)
GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}"
- printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.28.1/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"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.1,squid=sha256:9d428af47899bf18ef2d5618075777d76ef344c91e76c1f44ec1aaa0ee347e5f,agent=sha256:5e3f6ee27eeae07195838b97ac4aa2f8aea42a7c55f1c0d3e17d8e88e294ad0d,api-proxy=sha256:288e7d2a12d5b430500d739f9c16e20bb1ed51b91f986f3f3eccde189f489f5c,cli-proxy=sha256:f931e5e1e13f765605d03ef9511fc755d779a51b76581ea14586e9871506a610\"},\"logging\":{\"proxyLogsDir\":\"/tmp/gh-aw/sandbox/firewall/logs\",\"auditDir\":\"/tmp/gh-aw/sandbox/firewall/audit\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json"
+ printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.44/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"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-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.27.44,squid=sha256:83e48bbe12c634be8c228a576832fe45f66c529ac3659db92bddbcf2eeb6d627,agent=sha256:0d727725c737b58c7bdf51f640cffb928385ec46517e0917c7f1a02f1bada8b4,api-proxy=sha256:b50fbadba138f6e9aba94aca09711335c489bb3b15861220cb66f6092e042dc7,cli-proxy=sha256:c064d15974f7c933ec7d3f7b4038f4fd203547b3154bdc821afd379144887eff\"},\"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=""
@@ -1426,10 +1398,9 @@ jobs:
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 --log-level info --skip-pull \
- -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -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-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
+ 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 --log-level info --skip-pull \
+ -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -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 /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log
env:
- GITHUB_STEP_SUMMARY: /tmp/gh-aw/step-summary.md
AWF_REFLECT_ENABLED: 1
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode
@@ -1441,13 +1412,14 @@ jobs:
GH_AW_PHASE: detection
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
GH_AW_TIMEOUT_MINUTES: 20
- GH_AW_VERSION: v0.87.1
+ GH_AW_VERSION: v0.85.4
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]
@@ -1456,23 +1428,6 @@ jobs:
RUNNER_TEMP: ${{ runner.temp }}
S2STOKENS: true
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: Echo detection step summary
- if: always() && steps.detection_guard.outputs.run_detection == 'true'
- continue-on-error: true
- run: |
- if [ -s /tmp/gh-aw/step-summary.md ]; then
- cat /tmp/gh-aw/step-summary.md
- fi
- - 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 { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs');
- setupGlobals(core, github, context, exec, io, getOctokit);
- const { main } = require('${{ runner.temp }}/gh-aw/actions/render_detection_log.cjs');
- await main();
- name: Parse threat detection token usage for step summary
id: parse_detection_token_usage
if: always()
@@ -1540,15 +1495,15 @@ jobs:
steps:
- name: Setup Scripts
id: setup
- uses: github/gh-aw-actions/setup@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
GH_AW_INFO_ENGINE_ID: "copilot"
- name: Check team membership for workflow
id: check_membership
@@ -1596,20 +1551,15 @@ jobs:
comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }}
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@423b3dc04bbf1b1797194a4a75aa5cf5d0d4f5b3 # v0.87.1
+ uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9
with:
destination: ${{ runner.temp }}/gh-aw/actions
job-name: ${{ github.job }}
@@ -1618,16 +1568,15 @@ jobs:
env:
GH_AW_SETUP_WORKFLOW_NAME: "Python Management SDK PR Review"
GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/mgmt-sdk-pr-review.lock.yml@${{ github.ref }}
- GH_AW_INFO_VERSION: "1.0.80"
- GH_AW_INFO_AWF_VERSION: "v0.28.1"
+ GH_AW_INFO_VERSION: "1.0.78"
+ GH_AW_INFO_AWF_VERSION: "v0.27.44"
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
+ name: agent
path: /tmp/gh-aw/
- name: Setup agent output environment variable
id: setup-agent-output-env
@@ -1635,9 +1584,7 @@ jobs:
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
+ echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT"
- name: Configure GH_HOST for enterprise compatibility
id: ghes-host-config
shell: bash
diff --git a/.github/workflows/mgmt-sdk-pr-review.md b/.github/workflows/mgmt-sdk-pr-review.md
index 28b2f71a9ebe..323f55ff0607 100644
--- a/.github/workflows/mgmt-sdk-pr-review.md
+++ b/.github/workflows/mgmt-sdk-pr-review.md
@@ -247,6 +247,55 @@ steps:
output.write("\n")
PY
+ # Fetch only from the trusted base revision. Never execute the pull request's copy of this script.
+ - name: Collect breaking-change attribution context
+ shell: bash
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPOSITORY: ${{ github.repository }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ TRUSTED_BASE_SHA: ${{ github.event.pull_request.base.sha }}
+ run: |
+ python - <<'PY'
+ import base64
+ import json
+ import os
+ import pathlib
+ import re
+ import urllib.parse
+ import urllib.request
+
+ repository = os.environ["GH_REPOSITORY"]
+ revision = os.environ["TRUSTED_BASE_SHA"]
+ if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repository):
+ raise SystemExit("Invalid repository reference")
+ if not re.fullmatch(r"[0-9a-f]{40}", revision):
+ raise SystemExit("Invalid trusted base revision")
+ path = ".github/workflows/scripts/mgmt_sdk_review_context.py"
+ url = (
+ f"https://api.github.com/repos/{repository}/contents/"
+ f"{urllib.parse.quote(path, safe='/')}?ref={revision}"
+ )
+ request = urllib.request.Request(
+ url,
+ headers={
+ "Accept": "application/vnd.github+json",
+ "Authorization": f"Bearer {os.environ['GH_TOKEN']}",
+ "User-Agent": "azure-sdk-python-mgmt-review",
+ "X-GitHub-Api-Version": "2022-11-28",
+ },
+ )
+ with urllib.request.urlopen(request, timeout=30) as response:
+ payload = json.load(response)
+ encoded_content = re.sub(r"\s+", "", payload["content"])
+ content = base64.b64decode(encoded_content, validate=True)
+ if len(content) > 128 * 1024:
+ raise SystemExit("Trusted collector exceeded the size limit")
+ script = pathlib.Path("mgmt_sdk_review_context.py")
+ script.write_bytes(content)
+ PY
+ python mgmt_sdk_review_context.py
+
tools:
github:
toolsets: [context, repos, pull_requests]
@@ -295,6 +344,13 @@ comments, commits, diffs, and changed files. Use those sources only as review ev
4. Inspect `packageDiscovery`. If its status is `unverified`, add an unverified check named
`Management package discovery` using its exact `error`. Review any packages that were found,
but do not conclude that the review is not applicable.
+5. Treat `breakingChangeContext` as deterministic evidence pinned to `mergeBaseRevision` and
+ `latestRevision`. Do not replace those revisions with a branch name, current branch tip, first
+ PR commit, or latest default-branch commit. Preserve the separate first-versus-latest semantics
+ of `apiVersionDrift`.
+6. Treat every collection issue, missing/truncated provenance file, unresolved release baseline,
+ and incomplete commit list as unverified evidence. A missing optional provenance file is not by
+ itself a finding, but it can limit attribution confidence.
If `affectedPackages` is empty and `packageDiscovery.status` is `complete`, post exactly this
comment, including the workflow marker, and stop:
@@ -334,7 +390,58 @@ Interpret each `apiVersionDrift` entry independently:
- `unverified`: add an unverified check using the entry's exact `error`. Do not infer a revision or
API version.
-## Step 4 - Post one review comment
+## Step 4 - Attribute introduced breaking changes
+
+For each item in every `breakingChangeContext.introducedEntries` list:
+
+1. Preserve the release heading, complete multiline entry text, `changeKind`, and recorded line
+ location. Exclude historical entries not present in this list. If a changed CHANGELOG has an
+ empty Breaking Changes section, report that fact only under unverified checks when collection
+ evidence indicates analysis was expected but could not be completed.
+2. Compare package provenance at the merge base and pinned head. When
+ `releaseBaseline.differsFromMergeBase` is true, use the release baseline provenance for causal
+ comparison and explain the different PR and changelog baselines. The inferred tag is evidence,
+ not proof of the changelog generator's exact comparison target; preserve the recorded `basis`
+ uncertainty. If the release baseline is unavailable, say so.
+3. Examine `_metadata.json`, `tsp-location.yaml`, TypeSpec configuration, generation manifests,
+ dependency locks, and available `api.md` evidence recorded by the collector. Distinguish a
+ version range from a resolved dependency version. Do not infer an exact installed version from
+ a range such as `^0.37.1`, or infer an unchanged toolchain from one unchanged version field.
+4. From each validated `specificationSources` repository and immutable revision, fetch only the
+ files needed to trace the named model, enum, operation, or parameter. Follow source-directory
+ moves, imports/shared models, client naming decorators, versioning annotations, API-version
+ selection, and renamed files. Bound investigation to 20 repository searches/file fetches and
+ 1 MiB of fetched text per package. Validate repository names and full 40-character SHAs before
+ fetching. Surface access failures, search truncation, ambiguous matches, and exhausted limits.
+5. When toolchain causation is plausible, inspect immutable release notes, changelogs, or source
+ for the specifically implicated emitter/compiler/generator behavior. An emitter version bump
+ alone is not causal evidence. A specification commit change alone is not causal evidence.
+ Configuration changes must be named as configuration changes, not automatically categorized as
+ emitter changes. Do not perform old/new specification by old/new toolchain regeneration.
+6. Prefer permitted API artifacts such as `api.md` when available. Do not fetch or analyze files
+ excluded by the authoritative review rules merely to bypass those exclusions. Never execute,
+ build, import, regenerate, or check out pull-request-controlled code.
+
+Classify each entry using exactly one cause:
+
+- `TypeSpec/API`: a specific source definition, decorator, versioning annotation, or API-version
+ selection change explains the SDK change.
+- `Emitter/toolchain`: a specifically documented or source-supported generation behavior change
+ explains the SDK change after source and configuration differences are accounted for.
+- `Mixed`: evidence identifies concrete contributions from both TypeSpec/API and toolchain.
+- `Unverified`: available evidence cannot distinguish the cause or establish the relevant baseline.
+
+For confidence, use `High`, `Medium`, or `Low` and give an evidence-based rationale. `High` requires
+direct immutable evidence that accounts for plausible alternatives. `Medium` requires corroborated
+evidence with a named gap. `Low` means circumstantial or incomplete evidence and normally pairs
+with `Unverified`. For `Unverified`, state the specific evidence needed to resolve the attribution.
+Link only to immutable commit, tag-object, or release URLs. Do not claim candidate replacements are
+proven mappings without source evidence connecting them.
+
+Attribution is explanatory. Do not create or escalate a rule-violation finding solely because a
+breaking change is classified, including `Unverified`.
+
+## Step 5 - Post one review comment
Post exactly one comment through the `add-comment` safe output. Begin with this marker:
@@ -377,6 +484,21 @@ If every check was verified, replace that table with:
**Unverified checks:** None.
```
+Then include a distinct attribution section after unverified checks:
+
+```markdown
+### Breaking-change attribution
+
+| Package / release | Changelog entry | Cause | Evidence and explanation | Confidence |
+| --- | --- | --- | --- | --- |
+| Package and release heading | Full introduced or modified entry | `TypeSpec/API`, `Emitter/toolchain`, `Mixed`, or `Unverified` | Immutable links, baseline, concise explanation, and specific missing evidence when unverified | `High`, `Medium`, or `Low` with rationale |
+```
+
+Use one row per introduced entry. Preserve multiline entry meaning while converting line breaks to
+`
`, and escape Markdown table delimiters. If no introduced Breaking Changes entries were found
+and collection completed, write `**Breaking-change attribution:** No newly added or modified
+entries.` Do not merge attribution rows into the findings table.
+
Finish with a brief `### Review summary` naming every affected package and the checks completed.
## Constraints
diff --git a/.github/workflows/scripts/mgmt_sdk_review_context.py b/.github/workflows/scripts/mgmt_sdk_review_context.py
new file mode 100644
index 000000000000..e5795f53c0b6
--- /dev/null
+++ b/.github/workflows/scripts/mgmt_sdk_review_context.py
@@ -0,0 +1,649 @@
+#!/usr/bin/env python3
+"""Collect immutable, non-executable evidence for the management SDK PR reviewer."""
+
+import base64
+import binascii
+import difflib
+import json
+import os
+import re
+import urllib.error
+import urllib.parse
+import urllib.request
+
+
+API_ROOT = os.environ.get("GH_API_ROOT", "https://api.github.com")
+MAX_API_RESPONSE_BYTES = 12 * 1024 * 1024
+MAX_TEXT_FILE_BYTES = 256 * 1024
+MAX_PAGES = 30
+MAX_API_REQUESTS = 500
+API_TIMEOUT_SECONDS = 30
+PACKAGE_PATTERN = re.compile(r"^(sdk/[^/]+/azure-mgmt-[^/]+)(?:/|$)")
+REPOSITORY_PATTERN = re.compile(r"^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$")
+SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$")
+RELEASE_HEADING = re.compile(r"^##\s+(.+?)\s*$")
+SECTION_HEADING = re.compile(r"^(#{1,6})\s+(.+?)\s*$")
+BULLET = re.compile(r"^\s*[-*]\s+(.*)$")
+VERSION_LIKE_KEY = re.compile(
+ r"typespec|emitter|compiler|generator|client-generator|http-client-python|autorest",
+ re.IGNORECASE,
+)
+PROVENANCE_PATHS = (
+ "_metadata.json",
+ "tsp-location.yaml",
+ "api.metadata.yml",
+ "pyproject.toml",
+ "TempTypeSpecFiles/package-lock.json",
+)
+
+
+class GitHubApiError(RuntimeError):
+ def __init__(self, message, status=None):
+ super().__init__(message)
+ self.status = status
+
+
+class GitHubClient:
+ def __init__(self, repository, token, api_root=API_ROOT):
+ if not REPOSITORY_PATTERN.fullmatch(repository):
+ raise ValueError(f"Invalid GitHub repository reference: {repository!r}")
+ self.repository = repository
+ self.token = token
+ self.api_root = api_root.rstrip("/")
+ self.request_count = 0
+
+ def get(self, path):
+ if not path.startswith("/"):
+ raise ValueError("GitHub API paths must be absolute")
+ if self.request_count >= MAX_API_REQUESTS:
+ raise GitHubApiError(f"GitHub API request limit ({MAX_API_REQUESTS}) was reached")
+ self.request_count += 1
+ request = urllib.request.Request(
+ f"{self.api_root}{path}",
+ headers={
+ "Accept": "application/vnd.github+json",
+ "Authorization": f"Bearer {self.token}",
+ "User-Agent": "azure-sdk-python-mgmt-review",
+ "X-GitHub-Api-Version": "2022-11-28",
+ },
+ )
+ try:
+ with urllib.request.urlopen(request, timeout=API_TIMEOUT_SECONDS) as response:
+ content_length = response.headers.get("Content-Length")
+ if content_length and int(content_length) > MAX_API_RESPONSE_BYTES:
+ raise GitHubApiError(f"GitHub API response exceeded the size limit for {path}")
+ payload = response.read(MAX_API_RESPONSE_BYTES + 1)
+ if len(payload) > MAX_API_RESPONSE_BYTES:
+ raise GitHubApiError(f"GitHub API response exceeded the size limit for {path}")
+ return json.loads(payload.decode("utf-8"))
+ except urllib.error.HTTPError as error:
+ detail = error.read(4096).decode("utf-8", errors="replace")
+ raise GitHubApiError(
+ f"GitHub API request failed ({error.code}) for {path}: {detail}", status=error.code
+ ) from error
+ except urllib.error.URLError as error:
+ raise GitHubApiError(f"GitHub API request failed for {path}: {error.reason}") from error
+ except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as error:
+ raise GitHubApiError(f"GitHub API returned invalid or oversized data for {path}: {error}") from error
+
+ def paged_get(self, path, max_items):
+ items = []
+ for page in range(1, MAX_PAGES + 1):
+ separator = "&" if "?" in path else "?"
+ batch = self.get(f"{path}{separator}per_page=100&page={page}")
+ if not isinstance(batch, list):
+ raise GitHubApiError(f"GitHub API returned a non-list response for {path}")
+ items.extend(batch)
+ if len(items) >= max_items:
+ return items[:max_items], len(batch) == 100
+ if len(batch) < 100:
+ return items, False
+ return items, True
+
+ def read_file(self, path, revision):
+ encoded_path = urllib.parse.quote(path, safe="/")
+ encoded_ref = urllib.parse.quote(revision, safe="")
+ api_path = f"/repos/{self.repository}/contents/{encoded_path}?ref={encoded_ref}"
+ try:
+ payload = self.get(api_path)
+ except GitHubApiError as error:
+ return {
+ "status": "missing" if error.status == 404 else "unverified",
+ "path": path,
+ "revision": revision,
+ "error": str(error),
+ }
+ try:
+ if not isinstance(payload, dict):
+ raise TypeError("GitHub API response was not an object")
+ if payload.get("type") != "file" or payload.get("encoding") != "base64":
+ raise ValueError("content was unavailable as a base64 file")
+ declared_size = payload.get("size")
+ if isinstance(declared_size, int) and declared_size > MAX_TEXT_FILE_BYTES:
+ return {
+ "status": "truncated",
+ "path": path,
+ "revision": revision,
+ "htmlUrl": payload.get("html_url"),
+ "size": declared_size,
+ "error": f"File exceeded the {MAX_TEXT_FILE_BYTES}-byte evidence limit",
+ }
+ encoded_content = re.sub(r"\s+", "", payload["content"])
+ content = base64.b64decode(encoded_content, validate=True)
+ if len(content) > MAX_TEXT_FILE_BYTES:
+ raise ValueError(f"decoded content exceeded {MAX_TEXT_FILE_BYTES} bytes")
+ return {
+ "status": "available",
+ "path": path,
+ "revision": revision,
+ "htmlUrl": payload.get("html_url"),
+ "sha": payload.get("sha"),
+ "content": content.decode("utf-8"),
+ }
+ except (binascii.Error, KeyError, TypeError, ValueError, UnicodeDecodeError) as error:
+ return {
+ "status": "unverified",
+ "path": path,
+ "revision": revision,
+ "error": f"Could not read {path} at {revision}: {error}",
+ }
+
+
+def parse_breaking_changes(content):
+ """Parse Breaking Changes bullets while preserving release and source lines."""
+ lines = content.splitlines()
+ entries = []
+ empty_sections = []
+ releases = []
+ release = None
+ index = 0
+ while index < len(lines):
+ release_match = RELEASE_HEADING.match(lines[index])
+ if release_match:
+ release = release_match.group(1)
+ releases.append({"heading": release, "line": index + 1})
+ index += 1
+ continue
+ heading_match = SECTION_HEADING.match(lines[index])
+ if not heading_match or len(heading_match.group(1)) != 3 or heading_match.group(2).lower() != "breaking changes":
+ index += 1
+ continue
+
+ section_line = index + 1
+ index += 1
+ section_entry_count = 0
+ while index < len(lines):
+ next_heading = SECTION_HEADING.match(lines[index])
+ if next_heading and len(next_heading.group(1)) <= 3:
+ break
+ bullet_match = BULLET.match(lines[index])
+ if not bullet_match:
+ index += 1
+ continue
+ start = index
+ entry_lines = [bullet_match.group(1).rstrip()]
+ index += 1
+ while index < len(lines):
+ if BULLET.match(lines[index]) or SECTION_HEADING.match(lines[index]):
+ break
+ entry_lines.append(lines[index].rstrip())
+ index += 1
+ while entry_lines and not entry_lines[-1]:
+ entry_lines.pop()
+ text = "\n".join(entry_lines).strip()
+ entries.append(
+ {
+ "release": release,
+ "text": text,
+ "startLine": start + 1,
+ "endLine": start + max(1, len(entry_lines)),
+ "sectionLine": section_line,
+ }
+ )
+ section_entry_count += 1
+ if section_entry_count == 0:
+ empty_sections.append({"release": release, "sectionLine": section_line})
+ return {"entries": entries, "emptySections": empty_sections, "releases": releases}
+
+
+def introduced_breaking_changes(old_entries, new_entries):
+ """Return new or modified target entries, excluding exact historical entries."""
+ unmatched_old = list(old_entries)
+ introduced = []
+ for new_entry in new_entries:
+ exact_index = next(
+ (
+ index
+ for index, old_entry in enumerate(unmatched_old)
+ if old_entry["release"] == new_entry["release"] and old_entry["text"] == new_entry["text"]
+ ),
+ None,
+ )
+ if exact_index is not None:
+ unmatched_old.pop(exact_index)
+ continue
+
+ candidates = [entry for entry in unmatched_old if entry["release"] == new_entry["release"]]
+ previous = None
+ similarity = 0.0
+ for candidate in candidates:
+ ratio = difflib.SequenceMatcher(None, candidate["text"], new_entry["text"]).ratio()
+ if ratio > similarity:
+ similarity = ratio
+ previous = candidate
+ result = dict(new_entry)
+ if previous is not None and similarity >= 0.55:
+ result["changeKind"] = "modified"
+ result["previousText"] = previous["text"]
+ unmatched_old.remove(previous)
+ else:
+ result["changeKind"] = "added"
+ result["previousText"] = None
+ introduced.append(result)
+ return introduced
+
+
+def parse_json_evidence(file_evidence):
+ if file_evidence.get("status") != "available":
+ return None, file_evidence.get("error")
+ try:
+ return json.loads(file_evidence["content"]), None
+ except (json.JSONDecodeError, TypeError) as error:
+ return None, f"Invalid JSON in {file_evidence['path']}: {error}"
+
+
+def version_value_kind(value):
+ if not isinstance(value, str):
+ return "other"
+ return "range" if re.search(r"[<>=~^*| ]", value) else "resolved"
+
+
+def extract_lock_versions(lock_data):
+ versions = []
+ packages = lock_data.get("packages", {}) if isinstance(lock_data, dict) else {}
+ if isinstance(packages, dict):
+ for path, details in packages.items():
+ if not isinstance(details, dict):
+ continue
+ name = path.rsplit("node_modules/", 1)[-1] if "node_modules/" in path else details.get("name", path)
+ version = details.get("version")
+ if VERSION_LIKE_KEY.search(str(name)) and isinstance(version, str):
+ versions.append({"name": name, "version": version, "kind": "resolved"})
+ return sorted(versions, key=lambda item: (item["name"], item["version"]))[:100]
+
+
+def parse_tsp_location(content):
+ result = {"additionalDirectories": []}
+ current_list = None
+ for raw_line in content.splitlines():
+ line = raw_line.strip()
+ if not line or line.startswith("#"):
+ continue
+ if line.startswith("-") and current_list:
+ result[current_list].append(line[1:].strip().strip("'\""))
+ continue
+ match = re.fullmatch(r"([A-Za-z][A-Za-z0-9]*):\s*(.*?)\s*", line)
+ if not match:
+ continue
+ key, value = match.groups()
+ if key == "additionalDirectories":
+ current_list = key
+ if value and value != "[]":
+ result[key].append(value.strip("'\""))
+ else:
+ current_list = None
+ result[key] = value.strip("'\"")
+ return result
+
+
+def summarize_provenance(files):
+ summary = {
+ "files": [],
+ "metadata": None,
+ "tspLocation": None,
+ "resolvedDependencies": [],
+ "issues": [],
+ }
+ for evidence in files:
+ compact = {key: value for key, value in evidence.items() if key != "content"}
+ summary["files"].append(compact)
+ if evidence["path"].endswith("_metadata.json"):
+ metadata, error = parse_json_evidence(evidence)
+ if error:
+ summary["issues"].append(error)
+ elif isinstance(metadata, dict):
+ selected = {}
+ for key, value in metadata.items():
+ if key in {
+ "apiVersion",
+ "apiVersions",
+ "commit",
+ "repository_url",
+ "typespec_src",
+ "typespecAdditionalOptions",
+ "emitterVersion",
+ "httpClientPythonVersion",
+ } or VERSION_LIKE_KEY.search(key):
+ selected[key] = {
+ "value": value,
+ "kind": version_value_kind(value),
+ }
+ summary["metadata"] = selected
+ elif evidence["path"].endswith("tsp-location.yaml") and evidence.get("status") == "available":
+ summary["tspLocation"] = parse_tsp_location(evidence["content"])
+ elif evidence["path"].endswith("package-lock.json") and evidence.get("status") == "available":
+ lock_data, error = parse_json_evidence(evidence)
+ if error:
+ summary["issues"].append(error)
+ else:
+ summary["resolvedDependencies"] = extract_lock_versions(lock_data)
+ elif evidence.get("status") == "available":
+ summary["files"][-1]["content"] = evidence["content"]
+ if evidence.get("status") in {"unverified", "truncated"}:
+ summary["issues"].append(evidence.get("error"))
+ metadata = summary.get("metadata") or {}
+ tsp_location = summary.get("tspLocation") or {}
+ comparisons = (("commit", "commit"), ("typespec_src", "directory"))
+ for metadata_key, location_key in comparisons:
+ metadata_value = (metadata.get(metadata_key) or {}).get("value")
+ location_value = tsp_location.get(location_key)
+ if metadata_value and location_value and metadata_value != location_value:
+ summary["issues"].append(
+ f"Conflicting provenance: _metadata.json {metadata_key}={metadata_value!r}, "
+ f"tsp-location.yaml {location_key}={location_value!r}"
+ )
+ repository_url = (metadata.get("repository_url") or {}).get("value")
+ location_repository = tsp_location.get("repo")
+ if repository_url and location_repository:
+ match = re.fullmatch(
+ r"https://github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?)(?:\.git)?/?", repository_url
+ )
+ metadata_repository = match.group(1) if match else repository_url
+ if metadata_repository.lower() != location_repository.lower():
+ summary["issues"].append(
+ "Conflicting provenance: _metadata.json repository_url="
+ f"{repository_url!r}, tsp-location.yaml repo={location_repository!r}"
+ )
+ return summary
+
+
+def metadata_api_version(provenance):
+ metadata = provenance.get("metadata") or {}
+ item = metadata.get("apiVersion") or {}
+ value = item.get("value")
+ return value if isinstance(value, str) and value else None
+
+
+def collect_provenance(client, package_path, revision):
+ files = [client.read_file(f"{package_path}/{relative_path}", revision) for relative_path in PROVENANCE_PATHS]
+ return summarize_provenance(files)
+
+
+def latest_release_version(parsed_changelog):
+ for release in parsed_changelog.get("releases", []):
+ version = release["heading"].split()[0]
+ if version != "0.0.0":
+ return version
+ return None
+
+
+def resolve_release_tag(client, package_name, version):
+ if not version:
+ return {"status": "unverified", "error": "No previous release heading was found at the merge base"}
+ tag = f"{package_name}_{version}"
+ encoded_tag = urllib.parse.quote(tag, safe="")
+ try:
+ ref = client.get(f"/repos/{client.repository}/git/ref/tags/{encoded_tag}")
+ if not isinstance(ref, dict):
+ raise GitHubApiError(f"Tag lookup for {tag} returned a non-object response")
+ target = ref.get("object", {})
+ if target.get("type") == "tag":
+ tag_payload = client.get(f"/repos/{client.repository}/git/tags/{target.get('sha')}")
+ if not isinstance(tag_payload, dict):
+ raise GitHubApiError(f"Annotated tag lookup for {tag} returned a non-object response")
+ target = tag_payload.get("object", {})
+ sha = target.get("sha")
+ if target.get("type") != "commit" or not isinstance(sha, str) or not SHA_PATTERN.fullmatch(sha):
+ raise GitHubApiError(f"Tag {tag} did not resolve to an immutable commit")
+ return {"status": "available", "tag": tag, "revision": sha}
+ except GitHubApiError as error:
+ return {"status": "unverified", "tag": tag, "error": str(error)}
+
+
+def validated_source_reference(provenance):
+ conflicts = [issue for issue in provenance.get("issues", []) if issue.startswith("Conflicting provenance:")]
+ if conflicts:
+ return {"status": "unverified", "error": "; ".join(conflicts)}
+ metadata = provenance.get("metadata") or {}
+ repository_item = metadata.get("repository_url") or {}
+ commit_item = metadata.get("commit") or {}
+ repository_url = repository_item.get("value")
+ commit = commit_item.get("value")
+ tsp_location = provenance.get("tspLocation") or {}
+ repository = tsp_location.get("repo")
+ if repository_url:
+ match = re.fullmatch(
+ r"https://github\.com/([A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+?)(?:\.git)?/?", repository_url
+ )
+ repository = match.group(1) if match else None
+ commit = commit or tsp_location.get("commit")
+ match = REPOSITORY_PATTERN.fullmatch(repository or "")
+ if not match or not isinstance(commit, str) or not SHA_PATTERN.fullmatch(commit):
+ return {
+ "status": "unverified",
+ "error": "Specification repository URL or commit was missing or invalid",
+ }
+ return {"status": "available", "repository": repository, "revision": commit}
+
+
+def collect():
+ repository = os.environ["GH_REPOSITORY"]
+ pr_number = int(os.environ["PR_NUMBER"])
+ client = GitHubClient(repository, os.environ["GH_TOKEN"])
+
+ repository_data = client.get(f"/repos/{repository}")
+ default_branch = repository_data.get("default_branch")
+ branch_data = client.get(f"/repos/{repository}/branches/{urllib.parse.quote(default_branch, safe='')}")
+ rules_revision = branch_data.get("commit", {}).get("sha")
+ if not isinstance(rules_revision, str) or not SHA_PATTERN.fullmatch(rules_revision):
+ raise GitHubApiError("Default branch metadata did not contain an immutable commit SHA")
+ rules_file = client.read_file(".github/copilot-instructions.md", rules_revision)
+ if rules_file.get("status") != "available":
+ raise GitHubApiError(rules_file.get("error", "Could not load review rules"))
+ lines = rules_file["content"].splitlines()
+ heading = "## MGMT SDK Code Review Rules"
+ try:
+ start = lines.index(heading)
+ except ValueError as error:
+ raise GitHubApiError(f"{heading} was not found in .github/copilot-instructions.md") from error
+ end = next((index for index in range(start + 1, len(lines)) if lines[index].startswith("## ")), len(lines))
+
+ pull_request = client.get(f"/repos/{repository}/pulls/{pr_number}")
+ expected_changed_files = pull_request.get("changed_files")
+ latest_revision = pull_request.get("head", {}).get("sha")
+ base_revision = pull_request.get("base", {}).get("sha")
+ if not isinstance(expected_changed_files, int) or expected_changed_files < 0:
+ raise GitHubApiError("Pull request metadata did not contain a valid changed_files count")
+ if not all(isinstance(value, str) and SHA_PATTERN.fullmatch(value) for value in (latest_revision, base_revision)):
+ raise GitHubApiError("Pull request metadata did not contain valid base and head SHAs")
+
+ compare = client.get(f"/repos/{repository}/compare/{base_revision}...{latest_revision}")
+ merge_base_revision = compare.get("merge_base_commit", {}).get("sha")
+ if not isinstance(merge_base_revision, str) or not SHA_PATTERN.fullmatch(merge_base_revision):
+ raise GitHubApiError("The compare API did not return a valid merge-base SHA")
+
+ changed_files, file_list_truncated = client.paged_get(
+ f"/repos/{repository}/pulls/{pr_number}/files", max_items=3000
+ )
+ package_discovery_complete = len(changed_files) == expected_changed_files and not file_list_truncated
+ package_discovery_error = None
+ if not package_discovery_complete:
+ package_discovery_error = (
+ "Management package discovery is incomplete: pull request metadata reports "
+ f"{expected_changed_files} changed files, the API returned {len(changed_files)}, "
+ "or the bounded pagination limit was reached."
+ )
+
+ package_paths = sorted(
+ {
+ match.group(1)
+ for item in changed_files
+ for field in ("filename", "previous_filename")
+ for path in (item.get(field),)
+ if isinstance(path, str)
+ for match in (PACKAGE_PATTERN.match(path),)
+ if match
+ }
+ )
+ commits, commits_truncated = client.paged_get(f"/repos/{repository}/pulls/{pr_number}/commits", max_items=250)
+ commit_shas = [item.get("sha") for item in commits if isinstance(item.get("sha"), str)]
+ if not commit_shas:
+ raise GitHubApiError("Pull request metadata returned an empty commit list")
+ first_revision = commit_shas[0]
+
+ drift_results = []
+ breaking_change_context = []
+ for package_path in package_paths:
+ first_provenance = collect_provenance(client, package_path, first_revision)
+ latest_provenance = collect_provenance(client, package_path, latest_revision)
+ first_api_version = metadata_api_version(first_provenance)
+ latest_api_version = metadata_api_version(latest_provenance)
+ drift_errors = first_provenance["issues"] + latest_provenance["issues"]
+ drift_results.append(
+ {
+ "packagePath": package_path,
+ "metadataPath": f"{package_path}/_metadata.json",
+ "status": (
+ "unverified"
+ if not first_api_version or not latest_api_version
+ else "unchanged" if first_api_version == latest_api_version else "changed"
+ ),
+ "firstRevision": first_revision,
+ "firstApiVersion": first_api_version,
+ "latestRevision": latest_revision,
+ "latestApiVersion": latest_api_version,
+ "error": "; ".join(drift_errors) if (not first_api_version or not latest_api_version) else None,
+ }
+ )
+
+ head_changelog_path = f"{package_path}/CHANGELOG.md"
+ changelog_change = next(
+ (item for item in changed_files if item.get("filename") == head_changelog_path), None
+ )
+ base_changelog_path = (
+ changelog_change.get("previous_filename")
+ if changelog_change and changelog_change.get("status") == "renamed"
+ else head_changelog_path
+ )
+ old_file = client.read_file(base_changelog_path, merge_base_revision)
+ new_file = client.read_file(head_changelog_path, latest_revision)
+ collection_issues = []
+ old_parsed = {"entries": [], "emptySections": [], "releases": []}
+ new_parsed = {"entries": [], "emptySections": [], "releases": []}
+ if old_file.get("status") == "available":
+ old_parsed = parse_breaking_changes(old_file["content"])
+ else:
+ collection_issues.append(
+ old_file.get("error") or f"Merge-base changelog was unavailable: {base_changelog_path}"
+ )
+ if new_file.get("status") == "available":
+ new_parsed = parse_breaking_changes(new_file["content"])
+ else:
+ collection_issues.append(new_file.get("error"))
+
+ introduced = introduced_breaking_changes(old_parsed["entries"], new_parsed["entries"])
+ previous_version = latest_release_version(old_parsed)
+ release_baseline = resolve_release_tag(client, package_path.rsplit("/", 1)[-1], previous_version)
+ if release_baseline["status"] == "available":
+ release_baseline["provenance"] = collect_provenance(
+ client, package_path, release_baseline["revision"]
+ )
+ release_baseline["differsFromMergeBase"] = release_baseline["revision"] != merge_base_revision
+ release_baseline["basis"] = (
+ "Inferred from the newest release heading at the merge base; the changelog generator's exact "
+ "comparison target is not recorded by CHANGELOG.md."
+ )
+ else:
+ collection_issues.append(release_baseline.get("error"))
+
+ merge_base_provenance = collect_provenance(client, package_path, merge_base_revision)
+ collection_issues.extend(merge_base_provenance["issues"])
+ collection_issues.extend(latest_provenance["issues"])
+ if release_baseline.get("provenance"):
+ collection_issues.extend(release_baseline["provenance"]["issues"])
+ collection_issues = list(dict.fromkeys(issue for issue in collection_issues if issue))
+ breaking_change_context.append(
+ {
+ "packagePath": package_path,
+ "changelogPath": head_changelog_path,
+ "baseChangelogPath": base_changelog_path,
+ "mergeBaseRevision": merge_base_revision,
+ "latestRevision": latest_revision,
+ "status": "unverified" if collection_issues else "complete",
+ "introducedEntries": introduced,
+ "emptyBreakingChangeSections": new_parsed["emptySections"],
+ "collectionIssues": [issue for issue in collection_issues if issue],
+ "releaseBaseline": release_baseline,
+ "provenance": {
+ "mergeBase": merge_base_provenance,
+ "latest": latest_provenance,
+ },
+ "specificationSources": {
+ "mergeBase": validated_source_reference(merge_base_provenance),
+ "latest": validated_source_reference(latest_provenance),
+ "release": (
+ validated_source_reference(release_baseline["provenance"])
+ if release_baseline.get("provenance")
+ else {"status": "unverified", "error": "Release provenance was unavailable"}
+ ),
+ },
+ }
+ )
+
+ context = {
+ "repository": repository,
+ "pullRequestNumber": pr_number,
+ "rulesSource": f".github/copilot-instructions.md@{rules_revision}",
+ "mgmtSdkCodeReviewRules": "\n".join(lines[start:end]).strip(),
+ "packageDiscovery": {
+ "status": "complete" if package_discovery_complete else "unverified",
+ "expectedChangedFiles": expected_changed_files,
+ "returnedChangedFiles": len(changed_files),
+ "error": package_discovery_error,
+ },
+ "affectedPackages": package_paths,
+ "changedFiles": [
+ {
+ "filename": item.get("filename"),
+ "previousFilename": item.get("previous_filename"),
+ "status": item.get("status"),
+ "additions": item.get("additions"),
+ "deletions": item.get("deletions"),
+ }
+ for item in changed_files
+ ],
+ "firstRevision": first_revision,
+ "latestRevision": latest_revision,
+ "mergeBaseRevision": merge_base_revision,
+ "commitDiscovery": {
+ "status": "unverified" if commits_truncated else "complete",
+ "error": "PR commit pagination reached its bound" if commits_truncated else None,
+ },
+ "apiVersionDrift": drift_results,
+ "breakingChangeContext": breaking_change_context,
+ "collectionLimits": {
+ "maxApiResponseBytes": MAX_API_RESPONSE_BYTES,
+ "maxTextFileBytes": MAX_TEXT_FILE_BYTES,
+ "maxPages": MAX_PAGES,
+ "maxApiRequests": MAX_API_REQUESTS,
+ "apiTimeoutSeconds": API_TIMEOUT_SECONDS,
+ "githubApiRequests": client.request_count,
+ },
+ }
+ with open("review-context.json", "w", encoding="utf-8") as output:
+ json.dump(context, output, indent=2)
+ output.write("\n")
+
+
+if __name__ == "__main__":
+ collect()
\ No newline at end of file
diff --git a/.github/workflows/tests/test_mgmt_sdk_review_context.py b/.github/workflows/tests/test_mgmt_sdk_review_context.py
new file mode 100644
index 000000000000..d347db0fff65
--- /dev/null
+++ b/.github/workflows/tests/test_mgmt_sdk_review_context.py
@@ -0,0 +1,229 @@
+import importlib.util
+import json
+from pathlib import Path
+import unittest
+
+
+SCRIPT = Path(__file__).parents[1] / "scripts" / "mgmt_sdk_review_context.py"
+SPEC = importlib.util.spec_from_file_location("mgmt_sdk_review_context", SCRIPT)
+MODULE = importlib.util.module_from_spec(SPEC)
+SPEC.loader.exec_module(MODULE)
+
+
+class BreakingChangeParserTests(unittest.TestCase):
+ def test_added_modified_multiline_and_historical_entries(self):
+ old = MODULE.parse_breaking_changes(
+ """# Release History
+
+## 2.0.0 (2026-01-01)
+### Breaking Changes
+ - Method `Widgets.get` was renamed.
+
+## 1.0.0 (2025-01-01)
+### Breaking Changes
+ - Historical entry.
+"""
+ )
+ new = MODULE.parse_breaking_changes(
+ """# Release History
+
+## 2.0.0 (2026-01-01)
+### Breaking Changes
+ - Method `Widgets.get` was renamed to `Widgets.fetch`.
+ Use `fetch` for new calls.
+ - Deleted model `OldWidget`.
+
+## 1.0.0 (2025-01-01)
+### Breaking Changes
+ - Historical entry.
+"""
+ )
+
+ introduced = MODULE.introduced_breaking_changes(old["entries"], new["entries"])
+
+ self.assertEqual(["modified", "added"], [entry["changeKind"] for entry in introduced])
+ self.assertIn("Use `fetch`", introduced[0]["text"])
+ self.assertEqual(5, introduced[0]["startLine"])
+ self.assertNotIn("Historical entry.", [entry["text"] for entry in introduced])
+
+ def test_empty_section_is_recorded(self):
+ parsed = MODULE.parse_breaking_changes(
+ """## 3.0.0 (2026-02-02)
+### Breaking Changes
+
+### Features Added
+- A feature
+"""
+ )
+
+ self.assertEqual([], parsed["entries"])
+ self.assertEqual([{"release": "3.0.0 (2026-02-02)", "sectionLine": 2}], parsed["emptySections"])
+
+ def test_multiple_releases_remain_separate(self):
+ parsed = MODULE.parse_breaking_changes(
+ """## 3.0.0 (2026-02-02)
+### Breaking Changes
+- New break
+## 2.0.0 (2026-01-01)
+### Breaking Changes
+- Old break
+"""
+ )
+
+ self.assertEqual(
+ ["3.0.0 (2026-02-02)", "2.0.0 (2026-01-01)"],
+ [entry["release"] for entry in parsed["entries"]],
+ )
+
+ def test_latest_release_uses_all_headings_and_skips_placeholder(self):
+ parsed = MODULE.parse_breaking_changes(
+ """## 0.0.0 (Unreleased)
+### Features Added
+- Pending
+## 1.2.0b1 (2026-01-02)
+### Features Added
+- Released
+## 1.0.0 (2025-01-01)
+### Breaking Changes
+- Old break
+"""
+ )
+
+ self.assertEqual("1.2.0b1", MODULE.latest_release_version(parsed))
+
+
+class ProvenanceTests(unittest.TestCase):
+ def test_ranges_are_not_reported_as_resolved_versions(self):
+ metadata = {
+ "status": "available",
+ "path": "pkg/_metadata.json",
+ "revision": "a" * 40,
+ "content": json.dumps(
+ {
+ "emitterVersion": "0.63.6",
+ "httpClientPythonVersion": "^0.37.1",
+ }
+ ),
+ }
+ lock = {
+ "status": "available",
+ "path": "pkg/TempTypeSpecFiles/package-lock.json",
+ "revision": "a" * 40,
+ "content": json.dumps(
+ {
+ "packages": {
+ "node_modules/@azure-tools/typespec-python": {"version": "0.63.6"},
+ "node_modules/@typespec/compiler": {"version": "1.4.0"},
+ }
+ }
+ ),
+ }
+
+ summary = MODULE.summarize_provenance([metadata, lock])
+
+ self.assertEqual("range", summary["metadata"]["httpClientPythonVersion"]["kind"])
+ self.assertEqual("resolved", summary["metadata"]["emitterVersion"]["kind"])
+ self.assertEqual(2, len(summary["resolvedDependencies"]))
+
+ def test_missing_and_truncated_evidence_are_explicit(self):
+ summary = MODULE.summarize_provenance(
+ [
+ {"status": "missing", "path": "pkg/tsp-location.yaml", "revision": "a" * 40, "error": "404"},
+ {
+ "status": "truncated",
+ "path": "pkg/TempTypeSpecFiles/package-lock.json",
+ "revision": "a" * 40,
+ "error": "too large",
+ },
+ ]
+ )
+
+ self.assertEqual(["too large"], summary["issues"])
+ self.assertEqual(["missing", "truncated"], [item["status"] for item in summary["files"]])
+
+ def test_source_reference_requires_github_url_and_full_sha(self):
+ invalid = {"metadata": {"repository_url": {"value": "https://example.com/specs"}, "commit": {"value": "main"}}}
+ valid = {
+ "metadata": {
+ "repository_url": {"value": "https://github.com/Azure/azure-rest-api-specs"},
+ "commit": {"value": "a" * 40},
+ }
+ }
+
+ self.assertEqual("unverified", MODULE.validated_source_reference(invalid)["status"])
+ self.assertEqual("available", MODULE.validated_source_reference(valid)["status"])
+
+ def test_tsp_location_supplies_release_provenance_and_reports_conflicts(self):
+ tsp_location = {
+ "status": "available",
+ "path": "pkg/tsp-location.yaml",
+ "revision": "a" * 40,
+ "content": (
+ "directory: specification/contoso/New\n"
+ f"commit: {'b' * 40}\n"
+ "repo: Azure/azure-rest-api-specs\n"
+ "additionalDirectories:\n"
+ " - specification/common-types/resource-management\n"
+ ),
+ }
+ metadata = {
+ "status": "available",
+ "path": "pkg/_metadata.json",
+ "revision": "a" * 40,
+ "content": json.dumps(
+ {
+ "typespec_src": "specification/contoso/Old",
+ "commit": "c" * 40,
+ "repository_url": "https://github.com/Azure/different-specs",
+ }
+ ),
+ }
+
+ release_summary = MODULE.summarize_provenance([tsp_location])
+ conflicted = MODULE.summarize_provenance([metadata, tsp_location])
+
+ self.assertEqual("available", MODULE.validated_source_reference(release_summary)["status"])
+ self.assertEqual(
+ ["specification/common-types/resource-management"],
+ release_summary["tspLocation"]["additionalDirectories"],
+ )
+ self.assertEqual(3, len(conflicted["issues"]))
+ self.assertEqual("unverified", MODULE.validated_source_reference(conflicted)["status"])
+
+
+class FailureHandlingTests(unittest.TestCase):
+ def test_invalid_repository_is_rejected(self):
+ with self.assertRaises(ValueError):
+ MODULE.GitHubClient("https://github.com/Azure/repo", "token")
+
+ def test_read_file_records_api_failure(self):
+ client = MODULE.GitHubClient("Azure/azure-sdk-for-python", "token")
+
+ def fail(_):
+ raise MODULE.GitHubApiError("rate limited", status=403)
+
+ client.get = fail
+ evidence = client.read_file("CHANGELOG.md", "a" * 40)
+
+ self.assertEqual("unverified", evidence["status"])
+ self.assertIn("rate limited", evidence["error"])
+
+ def test_read_file_records_malformed_success_payload(self):
+ client = MODULE.GitHubClient("Azure/azure-sdk-for-python", "token")
+ client.get = lambda _: []
+
+ evidence = client.read_file("CHANGELOG.md", "a" * 40)
+
+ self.assertEqual("unverified", evidence["status"])
+ self.assertIn("not an object", evidence["error"])
+
+ def test_request_limit_fails_before_network_access(self):
+ client = MODULE.GitHubClient("Azure/azure-sdk-for-python", "token")
+ client.request_count = MODULE.MAX_API_REQUESTS
+
+ with self.assertRaisesRegex(MODULE.GitHubApiError, "request limit"):
+ client.get("/repos/Azure/azure-sdk-for-python")
+
+
+if __name__ == "__main__":
+ unittest.main()
\ No newline at end of file