From fed93b144aed0579684eb3ce14fc2acb6a75a3c3 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:43:00 +0200 Subject: [PATCH 1/4] Auto-delete /review, /review rerun, and /review tests command comments Once a /review command is recognized and the commenter is authorized, delete the command comment to keep PR conversations clean. Unauthorized or invalid attempts are left visible. Each workflow cleans up its own command's comment, and a failed delete never fails the trigger. - review-trigger.yml: delete the /review comment after PR validation in the trigger-review job, and the /review rerun comment after eligibility resolution in mark-rerun-ready. Resolve-RerunEligibility.ps1 looks up the triggering comment by id, so deletion must run after it. - copilot-review-tests.md: grant the pre-activation job issues:write via on.permissions and add a github-script step that deletes the /review tests comment when the command is exact and the commenter is an authorized collaborator (write/maintain/admin). Recompiled .lock.yml to match (frontmatter_hash updated; agent job stays read-only). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/copilot-review-tests.lock.yml | 66 +++++++++++++++---- .github/workflows/copilot-review-tests.md | 34 ++++++++++ .github/workflows/review-trigger.yml | 33 ++++++++++ 3 files changed, 120 insertions(+), 13 deletions(-) diff --git a/.github/workflows/copilot-review-tests.lock.yml b/.github/workflows/copilot-review-tests.lock.yml index 5fd05e31d8e0..bbd2aa98275c 100644 --- a/.github/workflows/copilot-review-tests.lock.yml +++ b/.github/workflows/copilot-review-tests.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"53de81b2269a74e2ed986e4b5dde3d9548eb6501470a563a38241db242b0fe66","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"10bbec5501886010be43370f740c98eaf0642c51c0f8e461faaed9d81fa6fe3c","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -53,6 +53,8 @@ on: types: - created - edited + # permissions: # Permissions applied to pre-activation job + # issues: write # roles: # Roles processed as role check in pre-activation job # - admin # Roles processed as role check in pre-activation job # - maintain # Roles processed as role check in pre-activation job @@ -89,6 +91,12 @@ on: # else # echo "should_run=false" >> "$GITHUB_OUTPUT" # fi + # - if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + # name: Delete the /review tests command comment when authorized + # uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 + # with: + # github-token: ${{ github.token }} + # script: "// Only delete when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed delete must not block activation.\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\nconst commentId = context.payload.comment.id;\ntry {\n await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId });\n core.info(`Deleted /review tests command comment ${commentId}.`);\n} catch (e) {\n core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`);\n}\n" workflow_dispatch: inputs: aw_context: @@ -269,20 +277,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_9223401ff282ccea_EOF' + cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' - GH_AW_PROMPT_9223401ff282ccea_EOF + GH_AW_PROMPT_07c2e0092ad8ccd6_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_9223401ff282ccea_EOF' + cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_9223401ff282ccea_EOF + GH_AW_PROMPT_07c2e0092ad8ccd6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_9223401ff282ccea_EOF' + cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -311,15 +319,15 @@ jobs: {{/if}} - GH_AW_PROMPT_9223401ff282ccea_EOF + GH_AW_PROMPT_07c2e0092ad8ccd6_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_9223401ff282ccea_EOF' + cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' {{#runtime-import .github/workflows/copilot-review-tests.md}} - GH_AW_PROMPT_9223401ff282ccea_EOF + GH_AW_PROMPT_07c2e0092ad8ccd6_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -578,9 +586,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_c89f701ef87a38cc_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e3588a9c0e10c0c7_EOF' {"add_comment":{"footer":false,"hide_older_comments":true,"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_c89f701ef87a38cc_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_e3588a9c0e10c0c7_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -774,7 +782,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_050fb0338b54f25f_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_95d840af44e6cb74_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -815,7 +823,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_050fb0338b54f25f_EOF + GH_AW_MCP_CONFIG_95d840af44e6cb74_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1432,6 +1440,8 @@ jobs: (!(github.event_name == 'issue_comment') || !contains(fromJSON('["CONTRIBUTOR","FIRST_TIME_CONTRIBUTOR","FIRST_TIMER","MANNEQUIN","NONE"]'), github.event.comment.author_association)) && (!(github.event_name == 'pull_request_review_comment') || !contains(fromJSON('["CONTRIBUTOR","FIRST_TIME_CONTRIBUTOR","FIRST_TIMER","MANNEQUIN","NONE"]'), github.event.comment.author_association)) runs-on: ubuntu-slim + permissions: + issues: write outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} exact_command_result: ${{ steps.exact_command.outcome }} @@ -1494,6 +1504,36 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} EVENT_NAME: ${{ github.event_name }} ISSUE_PULL_REQUEST_URL: ${{ github.event.issue.pull_request.url }} + - name: Delete the /review tests command comment when authorized + if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + // Only delete when the command is exactly `/review tests` (should_run) AND the + // commenter is an authorized collaborator (write/maintain/admin). This mirrors + // the workflow's own role gate but is self-contained, so an unauthorized user's + // comment is always left visible. A failed delete must not block activation. + const { owner, repo } = context.repo; + const actor = context.actor; + let permission = 'none'; + try { + const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor }); + permission = res.data.permission; + } catch (e) { + core.info(`Permission lookup for ${actor} failed: ${e.message}`); + } + if (!['admin', 'maintain', 'write'].includes(permission)) { + core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); + return; + } + const commentId = context.payload.comment.id; + try { + await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId }); + core.info(`Deleted /review tests command comment ${commentId}.`); + } catch (e) { + core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`); + } safe_outputs: needs: diff --git a/.github/workflows/copilot-review-tests.md b/.github/workflows/copilot-review-tests.md index 49d1d7c68701..4459d6cc604c 100644 --- a/.github/workflows/copilot-review-tests.md +++ b/.github/workflows/copilot-review-tests.md @@ -9,6 +9,10 @@ on: pull_request_review_comment: [contributor, first_time_contributor, first_timer, mannequin, none] reaction: none status-comment: false + # Grant the pre-activation job (the on.steps below) issues:write so it can delete the + # triggering `/review tests` comment once the command is recognized and authorized. + permissions: + issues: write steps: - name: Confirm exact /review tests command id: exact_command @@ -28,6 +32,36 @@ on: else echo "should_run=false" >> "$GITHUB_OUTPUT" fi + - name: Delete the /review tests command comment when authorized + if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ github.token }} + script: | + // Only delete when the command is exactly `/review tests` (should_run) AND the + // commenter is an authorized collaborator (write/maintain/admin). This mirrors + // the workflow's own role gate but is self-contained, so an unauthorized user's + // comment is always left visible. A failed delete must not block activation. + const { owner, repo } = context.repo; + const actor = context.actor; + let permission = 'none'; + try { + const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor }); + permission = res.data.permission; + } catch (e) { + core.info(`Permission lookup for ${actor} failed: ${e.message}`); + } + if (!['admin', 'maintain', 'write'].includes(permission)) { + core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); + return; + } + const commentId = context.payload.comment.id; + try { + await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId }); + core.info(`Deleted /review tests command comment ${commentId}.`); + } catch (e) { + core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`); + } workflow_dispatch: inputs: pr_number: diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index eaf1c92c3c24..63f71941b85f 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -139,6 +139,23 @@ jobs: "Reason: ${{ steps.rerun.outputs.reason }}" >> $env:GITHUB_STEP_SUMMARY "Label: ${{ steps.rerun.outputs.label }}" >> $env:GITHUB_STEP_SUMMARY + - name: Delete the /review rerun command comment + if: github.event_name == 'issue_comment' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + # Runs after eligibility resolution, which reads this comment by id, so the + # comment must still exist while Resolve-RerunEligibility.ps1 runs. The actor + # was authorized by the permission check at the start of this job. A failed + # delete must never fail the job. + if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then + echo "Deleted /review rerun command comment ${COMMENT_ID}" + else + echo "::warning::Could not delete /review rerun command comment ${COMMENT_ID}" + fi + trigger-review: needs: match if: needs.match.outputs.matched == 'true' && needs.match.outputs.command == 'review' @@ -282,6 +299,22 @@ jobs: echo "### Reviewing PR #${PR_NUMBER}" >> "$GITHUB_STEP_SUMMARY" echo "${PR_TITLE}" >> "$GITHUB_STEP_SUMMARY" + - name: Delete the /review command comment + if: github.event_name == 'issue_comment' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + # The command is recognized, the actor is authorized, and the PR is open, so + # remove the command comment to keep the PR conversation clean. A failed delete + # must never fail the review trigger. + if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then + echo "Deleted /review command comment ${COMMENT_ID}" + else + echo "::warning::Could not delete /review command comment ${COMMENT_ID}" + fi + - name: Checkout repository scripts uses: actions/checkout@v4 with: From a9f2238aa3dc0c46b844ceb0f29f383fd78af603 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Tue, 16 Jun 2026 13:57:17 +0200 Subject: [PATCH 2/4] Address review: gate command-comment deletion to avoid silent data loss - /review rerun: only delete the command comment when a rerun was actually triggered (eligible == 'true'); ineligible no-op reruns keep the comment as the 'seen, nothing changed' signal and preserve the previous-rerun checkpoint. - /review: move the delete to the end of trigger-review, gated on the AzDO trigger succeeding (steps.trigger_azdo.outcome == 'success'), so a lock-skip or trigger failure no longer swallows the command with no recovery. - /review tests: skip deletion when the comment was edited rather than created (gh-aw slash_command fires on created+edited) so editing an existing comment to '/review tests' can't destroy it; recompiled the lock. - Note the inline allow-list must stay in sync with the roles: frontmatter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/copilot-review-tests.lock.yml | 36 ++++++++++------ .github/workflows/copilot-review-tests.md | 8 ++++ .github/workflows/review-trigger.yml | 43 ++++++++++--------- 3 files changed, 52 insertions(+), 35 deletions(-) diff --git a/.github/workflows/copilot-review-tests.lock.yml b/.github/workflows/copilot-review-tests.lock.yml index bbd2aa98275c..e236e49bc886 100644 --- a/.github/workflows/copilot-review-tests.lock.yml +++ b/.github/workflows/copilot-review-tests.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"10bbec5501886010be43370f740c98eaf0642c51c0f8e461faaed9d81fa6fe3c","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b1908f74f1f7ecc96ab5c9b95a3a7dc975706694f46c2034cde28886be55e146","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -96,7 +96,7 @@ on: # uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: # github-token: ${{ github.token }} - # script: "// Only delete when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed delete must not block activation.\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\nconst commentId = context.payload.comment.id;\ntry {\n await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId });\n core.info(`Deleted /review tests command comment ${commentId}.`);\n} catch (e) {\n core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`);\n}\n" + # script: "// Only delete when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed delete must not block activation.\n// Only act on newly-created comments. The gh-aw slash_command trigger also fires\n// on `edited`, so without this guard, editing any existing comment to say\n// `/review tests` would delete that comment (and its entire history).\nif (context.payload.action !== 'created') {\n core.info('Skipping delete: comment was edited, not created.');\n return;\n}\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\n// Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync.\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\nconst commentId = context.payload.comment.id;\ntry {\n await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId });\n core.info(`Deleted /review tests command comment ${commentId}.`);\n} catch (e) {\n core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`);\n}\n" workflow_dispatch: inputs: aw_context: @@ -277,20 +277,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' + cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' - GH_AW_PROMPT_07c2e0092ad8ccd6_EOF + GH_AW_PROMPT_5c8291f76b08a36a_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_07c2e0092ad8ccd6_EOF' + cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_07c2e0092ad8ccd6_EOF + GH_AW_PROMPT_5c8291f76b08a36a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' + cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -319,15 +319,15 @@ jobs: {{/if}} - GH_AW_PROMPT_07c2e0092ad8ccd6_EOF + GH_AW_PROMPT_5c8291f76b08a36a_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_07c2e0092ad8ccd6_EOF' + cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' {{#runtime-import .github/workflows/copilot-review-tests.md}} - GH_AW_PROMPT_07c2e0092ad8ccd6_EOF + GH_AW_PROMPT_5c8291f76b08a36a_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -586,9 +586,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e3588a9c0e10c0c7_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e97e301be3430389_EOF' {"add_comment":{"footer":false,"hide_older_comments":true,"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_e3588a9c0e10c0c7_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_e97e301be3430389_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -782,7 +782,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_95d840af44e6cb74_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_923a308e6c5e7799_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -823,7 +823,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_95d840af44e6cb74_EOF + GH_AW_MCP_CONFIG_923a308e6c5e7799_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1514,6 +1514,13 @@ jobs: // commenter is an authorized collaborator (write/maintain/admin). This mirrors // the workflow's own role gate but is self-contained, so an unauthorized user's // comment is always left visible. A failed delete must not block activation. + // Only act on newly-created comments. The gh-aw slash_command trigger also fires + // on `edited`, so without this guard, editing any existing comment to say + // `/review tests` would delete that comment (and its entire history). + if (context.payload.action !== 'created') { + core.info('Skipping delete: comment was edited, not created.'); + return; + } const { owner, repo } = context.repo; const actor = context.actor; let permission = 'none'; @@ -1523,6 +1530,7 @@ jobs: } catch (e) { core.info(`Permission lookup for ${actor} failed: ${e.message}`); } + // Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync. if (!['admin', 'maintain', 'write'].includes(permission)) { core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); return; diff --git a/.github/workflows/copilot-review-tests.md b/.github/workflows/copilot-review-tests.md index 4459d6cc604c..5d854389cafc 100644 --- a/.github/workflows/copilot-review-tests.md +++ b/.github/workflows/copilot-review-tests.md @@ -42,6 +42,13 @@ on: // commenter is an authorized collaborator (write/maintain/admin). This mirrors // the workflow's own role gate but is self-contained, so an unauthorized user's // comment is always left visible. A failed delete must not block activation. + // Only act on newly-created comments. The gh-aw slash_command trigger also fires + // on `edited`, so without this guard, editing any existing comment to say + // `/review tests` would delete that comment (and its entire history). + if (context.payload.action !== 'created') { + core.info('Skipping delete: comment was edited, not created.'); + return; + } const { owner, repo } = context.repo; const actor = context.actor; let permission = 'none'; @@ -51,6 +58,7 @@ on: } catch (e) { core.info(`Permission lookup for ${actor} failed: ${e.message}`); } + // Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync. if (!['admin', 'maintain', 'write'].includes(permission)) { core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); return; diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 63f71941b85f..90ad0790a7af 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -140,16 +140,17 @@ jobs: "Label: ${{ steps.rerun.outputs.label }}" >> $env:GITHUB_STEP_SUMMARY - name: Delete the /review rerun command comment - if: github.event_name == 'issue_comment' + if: github.event_name == 'issue_comment' && steps.rerun.outputs.eligible == 'true' env: GH_TOKEN: ${{ github.token }} REPO: ${{ github.repository }} COMMENT_ID: ${{ github.event.comment.id }} run: | - # Runs after eligibility resolution, which reads this comment by id, so the - # comment must still exist while Resolve-RerunEligibility.ps1 runs. The actor - # was authorized by the permission check at the start of this job. A failed - # delete must never fail the job. + # Only delete once a rerun was actually triggered (eligible == 'true'). Ineligible + # reruns (no-ai-summary / review-in-progress / no-new-activity) keep the comment as + # the implicit "seen, nothing changed" signal and preserve the previous-rerun + # checkpoint Get-LatestRerunCommentBefore relies on. Eligibility resolution already + # read this comment by id. A failed delete must never fail the job. if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then echo "Deleted /review rerun command comment ${COMMENT_ID}" else @@ -299,22 +300,6 @@ jobs: echo "### Reviewing PR #${PR_NUMBER}" >> "$GITHUB_STEP_SUMMARY" echo "${PR_TITLE}" >> "$GITHUB_STEP_SUMMARY" - - name: Delete the /review command comment - if: github.event_name == 'issue_comment' - env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - COMMENT_ID: ${{ github.event.comment.id }} - run: | - # The command is recognized, the actor is authorized, and the PR is open, so - # remove the command comment to keep the PR conversation clean. A failed delete - # must never fail the review trigger. - if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then - echo "Deleted /review command comment ${COMMENT_ID}" - else - echo "::warning::Could not delete /review command comment ${COMMENT_ID}" - fi - - name: Checkout repository scripts uses: actions/checkout@v4 with: @@ -486,3 +471,19 @@ jobs: run: | . .github/scripts/shared/Update-AgentLabels.ps1 Clear-AgentReviewInProgress -PRNumber $env:PR_NUMBER -Owner '${{ github.repository_owner }}' -Repo '${{ github.event.repository.name }}' | Out-Null + + - name: Delete the /review command comment + if: github.event_name == 'issue_comment' && steps.trigger_azdo.outcome == 'success' + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + COMMENT_ID: ${{ github.event.comment.id }} + run: | + # Delete only after the pipeline was actually triggered, so a lock-skip + # (locked == 'true') or a failed AzDO trigger leaves the /review comment visible + # for the user to retry. A failed delete must never fail the review trigger. + if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then + echo "Deleted /review command comment ${COMMENT_ID}" + else + echo "::warning::Could not delete /review command comment ${COMMENT_ID}" + fi From 35b301865add3b77518ba4313c551aedb4b19a8e Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Wed, 17 Jun 2026 15:00:49 +0200 Subject: [PATCH 3/4] Hide review command comments as resolved instead of deleting Deleting the /review, /review rerun, and /review tests command comments erased the durable comment history the rerun scanner replays via the REST API (Resolve-RerunEligibility.ps1, Query-RerunReadyPRs.ps1, Get-LatestRerunCommentBefore) to reconstruct rerun/review state, which could re-qualify unchanged commits and drop --branch/--platform options. Switch all three paths to the GraphQL minimizeComment(classifier: RESOLVED) mutation. Minimized comments are collapsed in the web UI (decluttering the conversation) but are still returned by the REST list endpoint, so the scanner keeps working. Minimizing needs the same issues:write scope deletion used; failures stay warn-only and never fail the trigger. gh-aw lock regenerated (v0.77.5): frontmatter_hash updated, body_hash unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/copilot-review-tests.lock.yml | 58 +++++++++++-------- .github/workflows/copilot-review-tests.md | 33 +++++++---- .github/workflows/review-trigger.yml | 46 +++++++++------ 3 files changed, 83 insertions(+), 54 deletions(-) diff --git a/.github/workflows/copilot-review-tests.lock.yml b/.github/workflows/copilot-review-tests.lock.yml index e236e49bc886..65b0d252055d 100644 --- a/.github/workflows/copilot-review-tests.lock.yml +++ b/.github/workflows/copilot-review-tests.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"b1908f74f1f7ecc96ab5c9b95a3a7dc975706694f46c2034cde28886be55e146","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"38ba893257098da12def084ab891989b25c09e307477d143e712f82b2a04bff6","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -92,11 +92,11 @@ on: # echo "should_run=false" >> "$GITHUB_OUTPUT" # fi # - if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' - # name: Delete the /review tests command comment when authorized + # name: Hide the /review tests command comment as resolved when authorized # uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # with: # github-token: ${{ github.token }} - # script: "// Only delete when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed delete must not block activation.\n// Only act on newly-created comments. The gh-aw slash_command trigger also fires\n// on `edited`, so without this guard, editing any existing comment to say\n// `/review tests` would delete that comment (and its entire history).\nif (context.payload.action !== 'created') {\n core.info('Skipping delete: comment was edited, not created.');\n return;\n}\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\n// Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync.\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\nconst commentId = context.payload.comment.id;\ntry {\n await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId });\n core.info(`Deleted /review tests command comment ${commentId}.`);\n} catch (e) {\n core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`);\n}\n" + # script: "// Only hide when the command is exactly `/review tests` (should_run) AND the\n// commenter is an authorized collaborator (write/maintain/admin). This mirrors\n// the workflow's own role gate but is self-contained, so an unauthorized user's\n// comment is always left visible. A failed hide must not block activation.\n// Only act on newly-created comments. The gh-aw slash_command trigger also fires\n// on `edited`, so without this guard, editing any existing comment to say\n// `/review tests` would minimize that comment (and collapse its entire history).\nif (context.payload.action !== 'created') {\n core.info('Skipping hide: comment was edited, not created.');\n return;\n}\nconst { owner, repo } = context.repo;\nconst actor = context.actor;\nlet permission = 'none';\ntry {\n const res = await github.rest.repos.getCollaboratorPermissionLevel({ owner, repo, username: actor });\n permission = res.data.permission;\n} catch (e) {\n core.info(`Permission lookup for ${actor} failed: ${e.message}`);\n}\n// Must mirror the workflow `roles:` frontmatter (admin/maintain/write) — keep in sync.\nif (!['admin', 'maintain', 'write'].includes(permission)) {\n core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`);\n return;\n}\n// Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's\n// REST comment history, and minimized comments are still returned by the REST list\n// endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id.\nconst subjectId = context.payload.comment.node_id;\ntry {\n await github.graphql(\n `mutation($id: ID!) {\n minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) {\n minimizedComment { isMinimized }\n }\n }`,\n { id: subjectId }\n );\n core.info(`Hid /review tests command comment ${subjectId} as resolved.`);\n} catch (e) {\n core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`);\n}\n" workflow_dispatch: inputs: aw_context: @@ -277,20 +277,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' + cat << 'GH_AW_PROMPT_d0edaddd708f8569_EOF' - GH_AW_PROMPT_5c8291f76b08a36a_EOF + GH_AW_PROMPT_d0edaddd708f8569_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_5c8291f76b08a36a_EOF' + cat << 'GH_AW_PROMPT_d0edaddd708f8569_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_5c8291f76b08a36a_EOF + GH_AW_PROMPT_d0edaddd708f8569_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' + cat << 'GH_AW_PROMPT_d0edaddd708f8569_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -319,15 +319,15 @@ jobs: {{/if}} - GH_AW_PROMPT_5c8291f76b08a36a_EOF + GH_AW_PROMPT_d0edaddd708f8569_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_5c8291f76b08a36a_EOF' + cat << 'GH_AW_PROMPT_d0edaddd708f8569_EOF' {{#runtime-import .github/workflows/copilot-review-tests.md}} - GH_AW_PROMPT_5c8291f76b08a36a_EOF + GH_AW_PROMPT_d0edaddd708f8569_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -586,9 +586,9 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_e97e301be3430389_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_21d537cab9c84867_EOF' {"add_comment":{"footer":false,"hide_older_comments":true,"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_e97e301be3430389_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_21d537cab9c84867_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -782,7 +782,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_923a308e6c5e7799_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_f3e4b55b1a1a1dd8_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -823,7 +823,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_923a308e6c5e7799_EOF + GH_AW_MCP_CONFIG_f3e4b55b1a1a1dd8_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1504,21 +1504,21 @@ jobs: COMMENT_BODY: ${{ github.event.comment.body }} EVENT_NAME: ${{ github.event_name }} ISSUE_PULL_REQUEST_URL: ${{ github.event.issue.pull_request.url }} - - name: Delete the /review tests command comment when authorized + - name: Hide the /review tests command comment as resolved when authorized if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ github.token }} script: | - // Only delete when the command is exactly `/review tests` (should_run) AND the + // Only hide when the command is exactly `/review tests` (should_run) AND the // commenter is an authorized collaborator (write/maintain/admin). This mirrors // the workflow's own role gate but is self-contained, so an unauthorized user's - // comment is always left visible. A failed delete must not block activation. + // comment is always left visible. A failed hide must not block activation. // Only act on newly-created comments. The gh-aw slash_command trigger also fires // on `edited`, so without this guard, editing any existing comment to say - // `/review tests` would delete that comment (and its entire history). + // `/review tests` would minimize that comment (and collapse its entire history). if (context.payload.action !== 'created') { - core.info('Skipping delete: comment was edited, not created.'); + core.info('Skipping hide: comment was edited, not created.'); return; } const { owner, repo } = context.repo; @@ -1535,12 +1535,22 @@ jobs: core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); return; } - const commentId = context.payload.comment.id; + // Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's + // REST comment history, and minimized comments are still returned by the REST list + // endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id. + const subjectId = context.payload.comment.node_id; try { - await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId }); - core.info(`Deleted /review tests command comment ${commentId}.`); + await github.graphql( + `mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) { + minimizedComment { isMinimized } + } + }`, + { id: subjectId } + ); + core.info(`Hid /review tests command comment ${subjectId} as resolved.`); } catch (e) { - core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`); + core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`); } safe_outputs: diff --git a/.github/workflows/copilot-review-tests.md b/.github/workflows/copilot-review-tests.md index 5d854389cafc..87e091513148 100644 --- a/.github/workflows/copilot-review-tests.md +++ b/.github/workflows/copilot-review-tests.md @@ -9,8 +9,9 @@ on: pull_request_review_comment: [contributor, first_time_contributor, first_timer, mannequin, none] reaction: none status-comment: false - # Grant the pre-activation job (the on.steps below) issues:write so it can delete the - # triggering `/review tests` comment once the command is recognized and authorized. + # Grant the pre-activation job (the on.steps below) issues:write so it can hide (minimize + # as resolved) the triggering `/review tests` comment once the command is recognized and + # authorized. Minimizing requires the same issues:write scope that deletion did. permissions: issues: write steps: @@ -32,21 +33,21 @@ on: else echo "should_run=false" >> "$GITHUB_OUTPUT" fi - - name: Delete the /review tests command comment when authorized + - name: Hide the /review tests command comment as resolved when authorized if: github.event_name == 'issue_comment' && steps.exact_command.outputs.should_run == 'true' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ github.token }} script: | - // Only delete when the command is exactly `/review tests` (should_run) AND the + // Only hide when the command is exactly `/review tests` (should_run) AND the // commenter is an authorized collaborator (write/maintain/admin). This mirrors // the workflow's own role gate but is self-contained, so an unauthorized user's - // comment is always left visible. A failed delete must not block activation. + // comment is always left visible. A failed hide must not block activation. // Only act on newly-created comments. The gh-aw slash_command trigger also fires // on `edited`, so without this guard, editing any existing comment to say - // `/review tests` would delete that comment (and its entire history). + // `/review tests` would minimize that comment (and collapse its entire history). if (context.payload.action !== 'created') { - core.info('Skipping delete: comment was edited, not created.'); + core.info('Skipping hide: comment was edited, not created.'); return; } const { owner, repo } = context.repo; @@ -63,12 +64,22 @@ on: core.info(`Actor ${actor} is not an authorized collaborator (${permission}); leaving the /review tests comment.`); return; } - const commentId = context.payload.comment.id; + // Minimize (hide as resolved) rather than delete: the rerun scanner replays the PR's + // REST comment history, and minimized comments are still returned by the REST list + // endpoint — only collapsed in the web UI. node_id is the comment's GraphQL global id. + const subjectId = context.payload.comment.node_id; try { - await github.rest.issues.deleteComment({ owner, repo, comment_id: commentId }); - core.info(`Deleted /review tests command comment ${commentId}.`); + await github.graphql( + `mutation($id: ID!) { + minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) { + minimizedComment { isMinimized } + } + }`, + { id: subjectId } + ); + core.info(`Hid /review tests command comment ${subjectId} as resolved.`); } catch (e) { - core.warning(`Could not delete /review tests command comment ${commentId}: ${e.message}`); + core.warning(`Could not hide /review tests command comment ${subjectId}: ${e.message}`); } workflow_dispatch: inputs: diff --git a/.github/workflows/review-trigger.yml b/.github/workflows/review-trigger.yml index 90ad0790a7af..69767ade6011 100644 --- a/.github/workflows/review-trigger.yml +++ b/.github/workflows/review-trigger.yml @@ -139,22 +139,26 @@ jobs: "Reason: ${{ steps.rerun.outputs.reason }}" >> $env:GITHUB_STEP_SUMMARY "Label: ${{ steps.rerun.outputs.label }}" >> $env:GITHUB_STEP_SUMMARY - - name: Delete the /review rerun command comment + - name: Hide the /review rerun command comment as resolved if: github.event_name == 'issue_comment' && steps.rerun.outputs.eligible == 'true' env: GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - COMMENT_ID: ${{ github.event.comment.id }} + COMMENT_NODE_ID: ${{ github.event.comment.node_id }} run: | - # Only delete once a rerun was actually triggered (eligible == 'true'). Ineligible - # reruns (no-ai-summary / review-in-progress / no-new-activity) keep the comment as - # the implicit "seen, nothing changed" signal and preserve the previous-rerun - # checkpoint Get-LatestRerunCommentBefore relies on. Eligibility resolution already - # read this comment by id. A failed delete must never fail the job. - if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then - echo "Deleted /review rerun command comment ${COMMENT_ID}" + # Only collapse once a rerun was actually triggered (eligible == 'true'). Ineligible + # reruns (no-ai-summary / review-in-progress / no-new-activity) keep the comment fully + # visible as the implicit "seen, nothing changed" signal. + # + # We MINIMIZE (hide as resolved) rather than delete: the rerun scanner replays the + # PR's REST comment history to reconstruct rerun state (Resolve-RerunEligibility.ps1 / + # Query-RerunReadyPRs.ps1 / Get-LatestRerunCommentBefore), and minimized comments are + # still returned by the REST list endpoint — only collapsed in the web UI. Deleting + # would erase that durable checkpoint and re-qualify unchanged commits. A failed hide + # must never fail the job. + if gh api graphql -f query='mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:RESOLVED}){minimizedComment{isMinimized}}}' -f id="$COMMENT_NODE_ID" --silent; then + echo "Hid /review rerun command comment ${COMMENT_NODE_ID} as resolved" else - echo "::warning::Could not delete /review rerun command comment ${COMMENT_ID}" + echo "::warning::Could not hide /review rerun command comment ${COMMENT_NODE_ID}" fi trigger-review: @@ -472,18 +476,22 @@ jobs: . .github/scripts/shared/Update-AgentLabels.ps1 Clear-AgentReviewInProgress -PRNumber $env:PR_NUMBER -Owner '${{ github.repository_owner }}' -Repo '${{ github.event.repository.name }}' | Out-Null - - name: Delete the /review command comment + - name: Hide the /review command comment as resolved if: github.event_name == 'issue_comment' && steps.trigger_azdo.outcome == 'success' env: GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - COMMENT_ID: ${{ github.event.comment.id }} + COMMENT_NODE_ID: ${{ github.event.comment.node_id }} run: | - # Delete only after the pipeline was actually triggered, so a lock-skip + # Collapse only after the pipeline was actually triggered, so a lock-skip # (locked == 'true') or a failed AzDO trigger leaves the /review comment visible - # for the user to retry. A failed delete must never fail the review trigger. - if gh api -X DELETE "repos/${REPO}/issues/comments/${COMMENT_ID}" --silent; then - echo "Deleted /review command comment ${COMMENT_ID}" + # for the user to retry. + # + # We MINIMIZE (hide as resolved) rather than delete so the command — and its + # --branch/--platform options — survives in the REST comment history that the rerun + # scanner replays. Minimized comments are still returned by the REST list endpoint; + # only collapsed in the web UI. A failed hide must never fail the review trigger. + if gh api graphql -f query='mutation($id:ID!){minimizeComment(input:{subjectId:$id,classifier:RESOLVED}){minimizedComment{isMinimized}}}' -f id="$COMMENT_NODE_ID" --silent; then + echo "Hid /review command comment ${COMMENT_NODE_ID} as resolved" else - echo "::warning::Could not delete /review command comment ${COMMENT_ID}" + echo "::warning::Could not hide /review command comment ${COMMENT_NODE_ID}" fi From fde7fecd4c5d43c1218af7d8bd11629333c93aee Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Thu, 18 Jun 2026 14:26:32 +0200 Subject: [PATCH 4/4] Recompile lock on merged tree (v0.79.8) Addresses Round 4 review findings: - Upgraded gh-aw compiler from v0.77.5 to v0.79.8 - Restored environment: gh-aw-agents gate to detection job - Updated actions-lock.json setup action to v0.79.8 - Container image digest pinning and supply-chain hardening intact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/aw/actions-lock.json | 6 +- .../workflows/copilot-review-tests.lock.yml | 261 ++++++++++++------ 2 files changed, 186 insertions(+), 81 deletions(-) diff --git a/.github/aw/actions-lock.json b/.github/aw/actions-lock.json index 40a9d68d3dd3..eeb7cbb1ce64 100644 --- a/.github/aw/actions-lock.json +++ b/.github/aw/actions-lock.json @@ -35,10 +35,10 @@ "version": "v7.0.1", "sha": "043fb46d1a93c77aae656e7c1c64a875d1fc6a0a" }, - "github/gh-aw-actions/setup@v0.77.5": { + "github/gh-aw-actions/setup@v0.79.8": { "repo": "github/gh-aw-actions/setup", - "version": "v0.77.5", - "sha": "3ea13c02d765410340d533515cb31a7eef2baaf0" + "version": "v0.79.8", + "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" }, "github/gh-aw/actions/setup@v0.43.19": { "repo": "github/gh-aw/actions/setup", diff --git a/.github/workflows/copilot-review-tests.lock.yml b/.github/workflows/copilot-review-tests.lock.yml index a37dd95b7002..ab7bdeff1ff8 100644 --- a/.github/workflows/copilot-review-tests.lock.yml +++ b/.github/workflows/copilot-review-tests.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"478424151672a1d00be2f61c9d74e9a372766a90126cf5b02fa0c03d0e1a1802","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.77.5","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"3ea13c02d765410340d533515cb31a7eef2baaf0","version":"v0.77.5"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.58"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.22"},{"image":"ghcr.io/github/github-mcp-server:v1.1.0"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"478424151672a1d00be2f61c9d74e9a372766a90126cf5b02fa0c03d0e1a1802","body_hash":"44becb5921a41041d2bfe4f01ab40a77a42cabe36e4b2f68d213eba783e7bde3","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-sonnet-4.6","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"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":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} +# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.77.5). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -31,21 +32,20 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 +# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.58 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.58 -# - ghcr.io/github/gh-aw-mcpg:v0.3.22 -# - ghcr.io/github/github-mcp-server:v1.1.0 -# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 +# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 +# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa +# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c name: "Review PR Test Failures" on: @@ -139,10 +139,15 @@ jobs: permissions: actions: read contents: read + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" + daily_ai_credits_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_exceeded == 'true' }} + daily_ai_credits_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_threshold || '' }} + daily_ai_credits_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_ai_credits_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} @@ -156,17 +161,18 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -174,16 +180,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-sonnet-4.6" - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AGENT_VERSION: "1.0.55" - GH_AW_INFO_CLI_VERSION: "v0.77.5" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AGENT_VERSION: "1.0.60" + GH_AW_INFO_CLI_VERSION: "v0.79.8" GH_AW_INFO_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","dev.azure.com","*.visualstudio.com","helix.dot.net","*.blob.core.windows.net","img.shields.io"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -194,8 +200,26 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Review PR Test Failures" + GH_AW_WORKFLOW_ID: "copilot-review-tests" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false sparse-checkout: | @@ -231,7 +255,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.77.5" + GH_AW_COMPILED_VERSION: "v0.79.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -271,20 +295,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_fdf40966b3d61e30_EOF' + cat << 'GH_AW_PROMPT_068efbf1d2f63f6d_EOF' - GH_AW_PROMPT_fdf40966b3d61e30_EOF + GH_AW_PROMPT_068efbf1d2f63f6d_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_fdf40966b3d61e30_EOF' + cat << 'GH_AW_PROMPT_068efbf1d2f63f6d_EOF' Tools: add_comment, missing_tool, missing_data, noop - GH_AW_PROMPT_fdf40966b3d61e30_EOF + GH_AW_PROMPT_068efbf1d2f63f6d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_fdf40966b3d61e30_EOF' + cat << 'GH_AW_PROMPT_068efbf1d2f63f6d_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -313,15 +337,15 @@ jobs: {{/if}} - GH_AW_PROMPT_fdf40966b3d61e30_EOF + GH_AW_PROMPT_068efbf1d2f63f6d_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_fdf40966b3d61e30_EOF' + cat << 'GH_AW_PROMPT_068efbf1d2f63f6d_EOF' {{#runtime-import .github/workflows/copilot-review-tests.md}} - GH_AW_PROMPT_fdf40966b3d61e30_EOF + GH_AW_PROMPT_068efbf1d2f63f6d_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -409,7 +433,7 @@ jobs: include-hidden-files: true path: | /tmp/gh-aw/aw_info.json - /tmp/gh-aw/model_multipliers.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt /tmp/gh-aw/aw-prompts/prompt-import-tree.json @@ -422,6 +446,7 @@ jobs: agent: needs: activation + if: needs.activation.outputs.daily_ai_credits_exceeded != 'true' runs-on: ubuntu-latest environment: gh-aw-agents permissions: @@ -439,9 +464,11 @@ jobs: GH_AW_WORKFLOW_ID_SANITIZED: copilotreviewtests outputs: agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} - effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} has_patch: ${{ steps.collect_output.outputs.has_patch }} inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} @@ -452,10 +479,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 }} + 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@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -464,8 +492,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -476,7 +504,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Create gh-aw temp directory @@ -527,7 +555,7 @@ jobs: - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -539,11 +567,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.55 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 - name: Determine automatic lockdown mode for GitHub MCP Server id: determine-automatic-lockdown uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 (source v9) @@ -575,15 +603,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.25.58 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 ghcr.io/github/gh-aw-firewall/squid:0.25.58 ghcr.io/github/gh-aw-mcpg:v0.3.22 ghcr.io/github/github-mcp-server:v1.1.0 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_f57e47fabe0ced62_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_2fbc93150996d70c_EOF' {"add_comment":{"footer":false,"hide_older_comments":true,"max":1,"target":"*"},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"false"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_f57e47fabe0ced62_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_2fbc93150996d70c_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -773,16 +801,16 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.22' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' - mkdir -p /home/runner/.copilot + 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_925d7211b5591930_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.0", + "container": "ghcr.io/github/github-mcp-server:v1.1.2", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -818,7 +846,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_925d7211b5591930_EOF + GH_AW_MCP_CONFIG_c6fee03c27b97257_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -847,14 +875,20 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.58/awf-config.schema.json","network":{"allowDomains":["*.blob.core.windows.net","*.githubusercontent.com","*.visualstudio.com","*.vsblob.vsassets.io","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dev.azure.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","helix.dot.net","host.docker.internal","img.shields.io","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.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","www.microsoft.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.58"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.blob.core.windows.net\",\"*.githubusercontent.com\",\"*.visualstudio.com\",\"*.vsblob.vsassets.io\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dev.azure.com\",\"dist.nuget.org\",\"docs.github.com\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"helix.dot.net\",\"host.docker.internal\",\"img.shields.io\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"pkgs.dev.azure.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\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" @@ -870,18 +904,19 @@ jobs: fi # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.6 - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.77.5 + GH_AW_TIMEOUT_MINUTES: 30 + GH_AW_VERSION: v0.79.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -896,7 +931,6 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner - name: Detect agent errors if: always() id: detect-agent-errors @@ -1065,7 +1099,7 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_ai_credits_exceeded == 'true') runs-on: ubuntu-slim environment: gh-aw-agents permissions: @@ -1084,7 +1118,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1093,8 +1127,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1110,6 +1144,40 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + if-no-files-found: ignore - name: Process no-op messages id: noop uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -1121,6 +1189,10 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "false" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "copilot-review-tests" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1192,7 +1264,11 @@ jobs: GH_AW_ENGINE_ID: "copilot" GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} - GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1200,12 +1276,14 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_AI_CREDITS_EXCEEDED: ${{ needs.activation.outputs.daily_ai_credits_exceeded }} + GH_AW_DAILY_AI_CREDITS_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_ai_credits_total_effective_tokens }} + GH_AW_DAILY_AI_CREDITS_THRESHOLD: ${{ needs.activation.outputs.daily_ai_credits_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "false" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "30" - GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -1221,16 +1299,18 @@ jobs: if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest + environment: gh-aw-agents permissions: contents: read outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1239,8 +1319,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1258,7 +1338,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false # --- Threat Detection --- @@ -1267,7 +1347,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.25.58 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.58 ghcr.io/github/gh-aw-firewall/squid:0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 - name: Check if detection needed id: detection_guard if: always() @@ -1286,12 +1366,13 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." @@ -1329,11 +1410,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.55 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.58 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1343,14 +1424,19 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.58/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,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.58"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" @@ -1366,16 +1452,18 @@ jobs: fi # shellcheck disable=SC1003 sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_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 + -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-sonnet-4.6 + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.77.5 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.79.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1389,7 +1477,19 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1449,15 +1549,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership @@ -1561,8 +1661,11 @@ jobs: contents: read issues: write pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/copilot-review-tests" GH_AW_COMMANDS: "[\"review\"]" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} @@ -1570,7 +1673,8 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-sonnet-4.6" - GH_AW_ENGINE_VERSION: "1.0.55" + GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "copilot-review-tests" GH_AW_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/copilot-review-tests.md" @@ -1586,7 +1690,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@3ea13c02d765410340d533515cb31a7eef2baaf0 # v0.77.5 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1595,8 +1699,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Review PR Test Failures" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/copilot-review-tests.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.55" - GH_AW_INFO_AWF_VERSION: "v0.25.58" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1615,6 +1719,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash + # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. run: | # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op.