diff --git a/.github/agents/dependabot-pr-reviewer.agent.md b/.github/agents/dependabot-pr-reviewer.agent.md index e31d0955..624f83df 100644 --- a/.github/agents/dependabot-pr-reviewer.agent.md +++ b/.github/agents/dependabot-pr-reviewer.agent.md @@ -93,21 +93,29 @@ Render the review body as markdown in this order: ## Validation Signal -The agent runs via `pull_request_target` and may execute BEFORE the -`PR Validation` orchestrator has completed. Treat the deterministic CI -conclusion as the canonical validation signal when it is available, and as -`pending` otherwise. Do not invoke `uv`, `pytest`, `npm ci`, `terraform`, -or `go` from the bash tool — those binaries live on the host runner and -are not visible inside the AWF firewall sandbox. +The agent runs via `workflow_run` AFTER the `PR Validation` orchestrator +has reached a terminal conclusion. The deterministic CI signal is therefore +always final — there is no `pending` or `in_progress:*` state to handle. +Do not invoke `uv`, `pytest`, `npm ci`, `terraform`, or `go` from the bash +tool — those binaries live on the host runner and are not visible inside +the AWF firewall sandbox. The orchestrator's overall conclusion is injected into the prompt as -`PR_VALIDATION_CONCLUSION` (one of `pending`, `in_progress:`, -`success`, `failure`, `cancelled`, `neutral`, `skipped`, `timed_out`, -`action_required`, or `unknown`). Map the touched surfaces to the -per-job check runs below and read each conclusion via the `github` MCP -`pull_requests` toolset (or `GET /repos/{owner}/{repo}/commits/{sha}/check-runs`). - -### Surface to Check Run Map +`PR_VALIDATION_CONCLUSION` (one of `success`, `failure`, `cancelled`, +`timed_out`, `neutral`, `skipped`, `action_required`, or `unknown`). +The list of failing per-surface check-runs (JSON array of +`{name, html_url, conclusion}`) is injected as `PR_VALIDATION_FAILING_CHECKS`. +Read both directly from the environment. Do NOT call +`checks.listForRef` or `GET /repos/{owner}/{repo}/commits/{sha}/check-runs` +— the workflow's resolver step already did that work. + +### Reference: Surface to Check Run Naming + +Informational reference for interpreting entries in +`PR_VALIDATION_FAILING_CHECKS`. The persona does NOT walk this map via the +checks API — the workflow's resolver step already enumerated failing +check-runs server-side. Use this table only to map a failing check name +back to the dependency surface it covers when composing the review body. | Surface | Authoritative check runs | | --- | --- | @@ -156,10 +164,10 @@ Include a `### Validation Signal` block in the per-package section of the review body with three parts: 1. **Deterministic CI:** quote the orchestrator conclusion as - `PR Validation: ` followed by a bullet list of the relevant - per-surface check runs from the map above with name, conclusion, and - `html_url`. When `conclusion != success`, list the failing job names - first. + `PR Validation: ` followed by a bullet list rendered from + `PR_VALIDATION_FAILING_CHECKS` (entries are `{name, html_url, conclusion}`). + When `conclusion != success`, list every failing entry. When + `conclusion == success`, state "all per-surface check-runs passed". 2. **Static impact reasoning:** one or two sentences citing the static checks above. Always include the Isaac Sim ABI line when `training/rl/requirements.txt` is in the diff, even on minor bumps. @@ -167,28 +175,34 @@ review body with three parts: violation, peer-dep conflict, breaking-changelog quote), prepend `⚠️ Maintainer review recommended` to the top of the review body once. -If the orchestrator conclusion is unavailable or still in progress -(`pending`, `in_progress:*`, or `unknown`; PR resolution failed; or the -check-runs API returns empty), state: -`⚠️ Deterministic CI conclusion not yet available; verdict is advisory only.` -and keep the verdict at `COMMENT`. +If `PR_VALIDATION_CONCLUSION` is `neutral`, `skipped`, or `unknown`, or if +`PR_DEPENDABOT_SKIP_REASON == 'pr-resolution-failed'`, prepend the caution +banner described in Verdict Adjustment and keep the verdict at `COMMENT`. ### Verdict Adjustment -* `PR_VALIDATION_CONCLUSION == success` AND every relevant per-surface check - is `success` AND no static check raises a concern → verdict MAY upgrade +Map every terminal conclusion explicitly. Under `workflow_run`, +`PR_VALIDATION_CONCLUSION` is always final — there are no `pending` or +`in_progress:*` branches to consider. + +* `PR_VALIDATION_CONCLUSION == success` AND no static check raises a + concern AND no sticky high-risk trigger fires → verdict MAY upgrade from `COMMENT` to `APPROVE`. Rationale must reference the orchestrator - conclusion plus the green per-surface checks by name. -* `PR_VALIDATION_CONCLUSION` is `failure`, `cancelled`, or `timed_out` → - verdict stays at `COMMENT`. Body MUST quote each failing per-surface - check name plus its `html_url`. Do NOT skip enrichment — maintainers rely - on the advisory output to triage which package in a grouped PR caused - the failure. -* `PR_VALIDATION_CONCLUSION` is `neutral`, `skipped`, `action_required`, - `pending`, `in_progress:*`, or `unknown` → verdict stays at `COMMENT`; - body explains the inconclusive or pending state. + conclusion plus a green `PR_VALIDATION_FAILING_CHECKS` (empty array). +* `PR_VALIDATION_CONCLUSION ∈ {failure, cancelled, timed_out, action_required}` + → verdict stays at `COMMENT`. Body MUST quote each entry from + `PR_VALIDATION_FAILING_CHECKS` (`name` plus `html_url`). Do NOT skip + enrichment — maintainers rely on the advisory output to triage which + package in a grouped PR caused the failure. +* `PR_VALIDATION_CONCLUSION ∈ {neutral, skipped, unknown}` OR + `PR_DEPENDABOT_SKIP_REASON == 'pr-resolution-failed'` → verdict stays + at `COMMENT`. Prepend the banner + `> [!CAUTION]` + `> Deterministic CI signal unavailable (\`{conclusion}\`); review is advisory only.` + to the top of the review body. * The Isaac Sim ABI guard is sticky: a `numpy` 2.x bump keeps the verdict - at `COMMENT` and forces the high-risk banner regardless of CI conclusion. + at `COMMENT` and forces the `⚠️ Maintainer review recommended` banner + regardless of CI conclusion. ## Forbidden Actions diff --git a/.github/workflows/aw-dependabot-pr-review.lock.yml b/.github/workflows/aw-dependabot-pr-review.lock.yml index 36da8614..b18154ae 100644 --- a/.github/workflows/aw-dependabot-pr-review.lock.yml +++ b/.github/workflows/aw-dependabot-pr-review.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"46234d20433a81ce7491f5c6933eb4c75977de922d188a0253fa2392eff22580","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"8280dd5a8cb5dbb66ef96aa1668da8329aa3d131e6be219cf8a5622c696ccc49","compiler_version":"v0.71.5","strict":true,"agent_id":"copilot"} # 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":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"373c709c69115d41ff229c7e5df9f8788daa9553"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"4a3601121dd01d1626a1e23e37211e3254c1c06c","version":"v6.4.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/setup-python","sha":"a309ff8b426b58ec0e2a45f0f869d46889d02405","version":"v6.2.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"astral-sh/setup-uv","sha":"08807647e7069bb48b6ef5acd8ec9567f424441b","version":"v8.1.0"},{"repo":"github/gh-aw-actions/setup","sha":"b8068426813005612b960b5ab0b8bd2c27142323","version":"v0.71.5"},{"repo":"hashicorp/setup-terraform","sha":"5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85","version":"5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85"},{"repo":"terraform-linters/setup-tflint","sha":"b480b8fcdaa6f2c577f8e4fa799e89e756bb7c93","version":"b480b8fcdaa6f2c577f8e4fa799e89e756bb7c93"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40","digest":"sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.40@sha256:14ff567e8d9d4c2fbc5e55c973488381c71d7e0fdbe72d30ee7b8a738fd86504"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40","digest":"sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.40@sha256:2883ca3e5ae9f330cafdd9345bfd4ae17fc8da36c96d4c9a1f76e922b4c45280"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40","digest":"sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.40@sha256:b084f4a2c771f584ee68084ced52fa6b3245197a1889645d817462d307d3ac51"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.6","digest":"sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.6@sha256:2bb8eef86006a4c5963c55616a9c51c32f27bfdecb023b8aa6f91f6718d9171c"},{"image":"ghcr.io/github/github-mcp-server:v1.0.3","digest":"sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.3@sha256:2ac27ef03461ef2b877031b838a7d1fd7f12b12d4ace7796d8cad91446d55959"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) @@ -59,40 +59,34 @@ name: "AW Dependabot PR Review" "on": - # bots: # Bots processed as bot check in pre-activation job - # - dependabot[bot] # Bots processed as bot check in pre-activation job - pull_request_target: + workflow_run: + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation branches: - - main + - dependabot/** types: - - opened - - synchronize - - reopened - # roles: # Roles processed as role check in pre-activation job - # - admin # Roles processed as role check in pre-activation job - # - maintainer # Roles processed as role check in pre-activation job - # - write # Roles processed as role check in pre-activation job + - completed + workflows: + - PR Validation permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" - cancel-in-progress: true + group: "gh-aw-${{ github.workflow }}" run-name: "AW Dependabot PR Review" jobs: activation: needs: pre_activation + # zizmor: ignore[dangerous-triggers] - workflow_run trigger is secured with role and fork validation if: > - needs.pre_activation.outputs.activated == 'true' && (github.event.pull_request != null && startsWith(github.event.pull_request.head.ref, 'dependabot/') && - github.event.pull_request.user.login == 'dependabot[bot]' && !github.event.pull_request.draft) + (needs.pre_activation.outputs.activated == 'true' && (github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.actor.login == 'dependabot[bot]' && contains(fromJSON('["success","failure","cancelled","timed_out","neutral","skipped","action_required"]'), + github.event.workflow_run.conclusion))) && (github.event_name != 'workflow_run' || github.event.workflow_run.repository.id == github.repository_id && (!(github.event.workflow_run.repository.fork))) runs-on: ubuntu-slim permissions: actions: read contents: read outputs: - body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} @@ -101,8 +95,6 @@ jobs: secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} - text: ${{ steps.sanitized.outputs.text }} - title: ${{ steps.sanitized.outputs.title }} steps: - name: Setup Scripts id: setup @@ -189,18 +181,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); await main(); - - name: Compute current body text - id: sanitized - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_ALLOWED_BOTS: "dependabot[bot]" - GH_AW_ALLOWED_DOMAINS: "*.docker.com,*.docker.io,*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.osv.dev,api.snapcraft.io,apt.releases.hashicorp.com,archive.ubuntu.com,auth.docker.io,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,bun.sh,cdn.jsdelivr.net,codeload.github.com,conda.anaconda.org,conda.binstar.org,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,deb.nodesource.com,deno.land,dl.k8s.io,docs.github.com,esm.sh,files.pythonhosted.org,gcr.io,get.pnpm.io,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,mcr.microsoft.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkg.go.dev,pkgs.k8s.io,ppa.launchpad.net,production.cloudflare.docker.com,proxy.golang.org,pypi.org,pypi.python.org,quay.io,raw.githubusercontent.com,registry.bower.io,registry.hub.docker.com,registry.npmjs.com,registry.npmjs.org,registry.terraform.io,registry.yarnpkg.com,releases.hashicorp.com,repo.anaconda.com,repo.continuum.io,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,services.nvd.nist.gov,skimdb.npmjs.com,storage.googleapis.com,sum.golang.org,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com,yum.releases.hashicorp.com" - 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/compute_text.cjs'); - await main(); - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -217,20 +197,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_5734b58a8c868ede_EOF' + cat << 'GH_AW_PROMPT_62143e0c441a6736_EOF' - GH_AW_PROMPT_5734b58a8c868ede_EOF + GH_AW_PROMPT_62143e0c441a6736_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_5734b58a8c868ede_EOF' + cat << 'GH_AW_PROMPT_62143e0c441a6736_EOF' Tools: add_comment(max:2), create_pull_request_review_comment(max:5), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_5734b58a8c868ede_EOF + GH_AW_PROMPT_62143e0c441a6736_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_5734b58a8c868ede_EOF' + cat << 'GH_AW_PROMPT_62143e0c441a6736_EOF' The following GitHub context information is available for this workflow: {{#if __GH_AW_GITHUB_ACTOR__ }} @@ -259,13 +239,13 @@ jobs: {{/if}} - GH_AW_PROMPT_5734b58a8c868ede_EOF + GH_AW_PROMPT_62143e0c441a6736_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_5734b58a8c868ede_EOF' + cat << 'GH_AW_PROMPT_62143e0c441a6736_EOF' {{#runtime-import .github/agents/dependabot-pr-reviewer.agent.md}} {{#runtime-import .github/workflows/aw-dependabot-pr-review.md}} - GH_AW_PROMPT_5734b58a8c868ede_EOF + GH_AW_PROMPT_62143e0c441a6736_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -344,8 +324,11 @@ jobs: runs-on: ubuntu-latest permissions: actions: read + checks: read contents: read pull-requests: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}-${{ github.event.workflow_run.head_sha }}" env: DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} GH_AW_ASSETS_ALLOWED_EXTS: "" @@ -443,10 +426,10 @@ jobs: with: tflint_version: latest - id: resolve-pr - name: Resolve Dependabot PR context and fetch PR Validation status + name: Resolve Dependabot PR context from workflow_run uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # 373c709c69115d41ff229c7e5df9f8788daa9553 with: - script: "const pr = context.payload.pull_request;\nif (!pr) {\n core.setFailed('pull_request payload missing');\n return;\n}\nif (pr.user.login !== 'dependabot[bot]') {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'not-dependabot');\n return;\n}\nif (pr.draft) {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'draft');\n return;\n}\ncore.exportVariable('PR_NUMBER', String(pr.number));\ncore.exportVariable('PR_TITLE', pr.title);\ncore.exportVariable('PR_HEAD_REF', pr.head.ref);\ncore.exportVariable('PR_BASE_REF', pr.base.ref);\ncore.exportVariable('PR_AUTHOR', pr.user.login);\ncore.exportVariable('PR_HEAD_SHA', pr.head.sha);\n\n// Look up the most recent PR Validation workflow run for this head SHA.\n// It may still be in progress when pull_request_target fires.\nlet conclusion = 'pending';\nlet runUrl = '';\ntry {\n const { data } = await github.rest.actions.listWorkflowRunsForRepo({\n owner: context.repo.owner,\n repo: context.repo.repo,\n head_sha: pr.head.sha,\n event: 'pull_request',\n per_page: 30,\n });\n const validation = (data.workflow_runs || []).find(r => r.name === 'PR Validation');\n if (validation) {\n runUrl = validation.html_url || '';\n conclusion = validation.status === 'completed'\n ? (validation.conclusion || 'unknown')\n : `in_progress:${validation.status}`;\n }\n} catch (err) {\n core.warning(`Failed to look up PR Validation run: ${err.message}`);\n}\ncore.exportVariable('PR_VALIDATION_CONCLUSION', conclusion);\ncore.exportVariable('PR_VALIDATION_RUN_URL', runUrl);\ncore.info(`Resolved PR #${pr.number} (${pr.title}); PR Validation conclusion: ${conclusion}`);\n" + script: "const run = context.payload.workflow_run;\nif (!run) {\n core.setFailed('workflow_run payload missing');\n return;\n}\nif (run.event !== 'pull_request') {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'not-a-pr-run');\n return;\n}\n\nlet pr = (run.pull_requests || [])[0];\nif (!pr) {\n // Fork PRs do not appear in workflow_run.pull_requests; fall back to search.\n // Filter to open Dependabot PRs with the exact head SHA to avoid ambiguity\n // when Dependabot opens several PRs in the same batch.\n const q = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:open author:app/dependabot sha:${run.head_sha}`;\n const { data } = await github.rest.search.issuesAndPullRequests({ q });\n const matches = (data.items || []).filter(i =>\n i.user.login === 'dependabot[bot]' && i.state === 'open');\n if (matches.length === 0) {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'pr-resolution-failed');\n return;\n }\n if (matches.length > 1) {\n core.setFailed(`Ambiguous PR resolution: ${matches.length} open Dependabot PRs match SHA ${run.head_sha}`);\n return;\n }\n const { data: full } = await github.rest.pulls.get({\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: matches[0].number,\n });\n pr = full;\n} else {\n // Hydrate the full PR object so fields like `body` and `draft` are reliable.\n const { data: full } = await github.rest.pulls.get({\n owner: context.repo.owner,\n repo: context.repo.repo,\n pull_number: pr.number,\n });\n pr = full;\n}\n\nif (pr.user.login !== 'dependabot[bot]') {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'not-dependabot');\n return;\n}\nif (pr.draft) {\n core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'draft');\n return;\n}\n\ncore.exportVariable('PR_NUMBER', String(pr.number));\ncore.exportVariable('PR_TITLE', pr.title);\ncore.exportVariable('PR_HEAD_REF', pr.head.ref);\ncore.exportVariable('PR_BASE_REF', pr.base.ref);\ncore.exportVariable('PR_AUTHOR', pr.user.login);\ncore.exportVariable('PR_HEAD_SHA', pr.head.sha);\n\n// PR Validation conclusion comes directly from the triggering workflow_run payload;\n// it is always final under `types: [completed]`.\ncore.exportVariable('PR_VALIDATION_CONCLUSION', run.conclusion);\ncore.exportVariable('PR_VALIDATION_RUN_URL', run.html_url || '');\n\n// Resolve per-surface check-runs ONCE here so the persona does not re-walk them.\n// Paginate to avoid silently missing checks when the matrix grows beyond a single page.\nlet failing = [];\ntry {\n const checkRuns = await github.paginate(github.rest.checks.listForRef, {\n owner: context.repo.owner,\n repo: context.repo.repo,\n ref: pr.head.sha,\n per_page: 100,\n }, response => response.data.check_runs);\n failing = checkRuns\n .filter(c => c.status === 'completed'\n && !['success', 'neutral', 'skipped'].includes(c.conclusion))\n .map(c => ({ name: c.name, html_url: c.html_url, conclusion: c.conclusion }));\n} catch (err) {\n core.warning(`Failed to enumerate check-runs: ${err.message}`);\n}\ncore.exportVariable('PR_VALIDATION_FAILING_CHECKS', JSON.stringify(failing));\n\n// Hydrate Dependabot enrichment input from REST so the agent does not depend on\n// the integrity-filtered MCP read of the PR body.\ncore.exportVariable('PR_BODY', pr.body || '');\n\ncore.info(`Resolved PR #${pr.number} (${pr.title}); PR Validation conclusion: ${run.conclusion}; failing checks: ${failing.length}`);\n" - name: Configure Git credentials env: @@ -509,15 +492,15 @@ 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_a6000a4bbc0dff64_EOF' - {"add_comment":{"max":2,"target":"triggering"},"create_pull_request_review_comment":{"max":5,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_a6000a4bbc0dff64_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_7bc49ded57060af8_EOF' + {"add_comment":{"max":2,"target":"${{ env.PR_NUMBER }}"},"create_pull_request_review_comment":{"max":5,"side":"RIGHT","target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"max":1,"target":"${{ env.PR_NUMBER }}"}} + GH_AW_SAFE_OUTPUTS_CONFIG_7bc49ded57060af8_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 2 comment(s) can be added. Target: triggering. Supports reply_to_id for discussion threading.", + "add_comment": " CONSTRAINTS: Maximum 2 comment(s) can be added. Target: ${{ env.PR_NUMBER }}. Supports reply_to_id for discussion threading.", "create_pull_request_review_comment": " CONSTRAINTS: Maximum 5 review comment(s) can be created. Comments will be on the RIGHT side of the diff.", "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted." }, @@ -757,7 +740,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_658c528aa9fae3bb_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_b5465a56f59782f6_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -798,7 +781,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_658c528aa9fae3bb_EOF + GH_AW_MCP_CONFIG_b5465a56f59782f6_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -1059,7 +1042,7 @@ jobs: issues: write pull-requests: write concurrency: - group: "gh-aw-conclusion-aw-dependabot-pr-review" + group: "gh-aw-conclusion-aw-dependabot-pr-review-${{ github.event.workflow_run.head_sha }}" cancel-in-progress: false outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} @@ -1378,8 +1361,8 @@ jobs: pre_activation: if: > - github.event.pull_request != null && startsWith(github.event.pull_request.head.ref, 'dependabot/') && - github.event.pull_request.user.login == 'dependabot[bot]' && !github.event.pull_request.draft + github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.actor.login == 'dependabot[bot]' && contains(fromJSON('["success","failure","cancelled","timed_out","neutral","skipped","action_required"]'), + github.event.workflow_run.conclusion) runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' }} @@ -1401,7 +1384,6 @@ jobs: uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REQUIRED_ROLES: "admin,maintainer,write" - GH_AW_ALLOWED_BOTS: "dependabot[bot]" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -1485,7 +1467,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.docker.com,*.docker.io,*.githubusercontent.com,*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.npms.io,api.osv.dev,api.snapcraft.io,apt.releases.hashicorp.com,archive.ubuntu.com,auth.docker.io,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,bun.sh,cdn.jsdelivr.net,codeload.github.com,conda.anaconda.org,conda.binstar.org,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,deb.nodesource.com,deno.land,dl.k8s.io,docs.github.com,esm.sh,files.pythonhosted.org,gcr.io,get.pnpm.io,ghcr.io,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,go.dev,golang.org,googleapis.deno.dev,googlechromelabs.github.io,goproxy.io,host.docker.internal,json-schema.org,json.schemastore.org,jsr.io,keyserver.ubuntu.com,lfs.github.com,mcr.microsoft.com,nodejs.org,npm.pkg.github.com,npmjs.com,npmjs.org,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,pkg.go.dev,pkgs.k8s.io,ppa.launchpad.net,production.cloudflare.docker.com,proxy.golang.org,pypi.org,pypi.python.org,quay.io,raw.githubusercontent.com,registry.bower.io,registry.hub.docker.com,registry.npmjs.com,registry.npmjs.org,registry.terraform.io,registry.yarnpkg.com,releases.hashicorp.com,repo.anaconda.com,repo.continuum.io,repo.yarnpkg.com,s.symcb.com,s.symcd.com,security.ubuntu.com,services.nvd.nist.gov,skimdb.npmjs.com,storage.googleapis.com,sum.golang.org,telemetry.enterprise.githubcopilot.com,telemetry.vercel.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.npmjs.com,www.npmjs.org,yarnpkg.com,yum.releases.hashicorp.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2,\"target\":\"triggering\"},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"max\":1}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":2,\"target\":\"${{ env.PR_NUMBER }}\"},\"create_pull_request_review_comment\":{\"max\":5,\"side\":\"RIGHT\",\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"max\":1,\"target\":\"${{ env.PR_NUMBER }}\"}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/aw-dependabot-pr-review.md b/.github/workflows/aw-dependabot-pr-review.md index d9b7f701..025e23b3 100644 --- a/.github/workflows/aw-dependabot-pr-review.md +++ b/.github/workflows/aw-dependabot-pr-review.md @@ -4,21 +4,22 @@ description: Advisory agentic review of Dependabot dependency update PRs for phy engine: copilot timeout-minutes: 15 if: > - github.event.pull_request != null && - startsWith(github.event.pull_request.head.ref, 'dependabot/') && - github.event.pull_request.user.login == 'dependabot[bot]' && - !github.event.pull_request.draft + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.actor.login == 'dependabot[bot]' && + contains(fromJSON('["success","failure","cancelled","timed_out","neutral","skipped","action_required"]'), + github.event.workflow_run.conclusion) on: - pull_request_target: - types: [opened, synchronize, reopened] - branches: - - main - bots: ["dependabot[bot]"] - roles: [admin, maintainer, write] + workflow_run: + workflows: ["PR Validation"] + types: [completed] + branches: ["dependabot/**"] +concurrency: + job-discriminator: ${{ github.event.workflow_run.head_sha }} permissions: contents: read pull-requests: read actions: read + checks: read network: allowed: - defaults @@ -64,16 +65,54 @@ steps: uses: terraform-linters/setup-tflint@b480b8fcdaa6f2c577f8e4fa799e89e756bb7c93 # v6.2.2 with: tflint_version: latest - - name: Resolve Dependabot PR context and fetch PR Validation status + - name: Resolve Dependabot PR context from workflow_run id: resolve-pr uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9.0.0 with: script: | - const pr = context.payload.pull_request; - if (!pr) { - core.setFailed('pull_request payload missing'); + const run = context.payload.workflow_run; + if (!run) { + core.setFailed('workflow_run payload missing'); + return; + } + if (run.event !== 'pull_request') { + core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'not-a-pr-run'); return; } + + let pr = (run.pull_requests || [])[0]; + if (!pr) { + // Fork PRs do not appear in workflow_run.pull_requests; fall back to search. + // Filter to open Dependabot PRs with the exact head SHA to avoid ambiguity + // when Dependabot opens several PRs in the same batch. + const q = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:open author:app/dependabot sha:${run.head_sha}`; + const { data } = await github.rest.search.issuesAndPullRequests({ q }); + const matches = (data.items || []).filter(i => + i.user.login === 'dependabot[bot]' && i.state === 'open'); + if (matches.length === 0) { + core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'pr-resolution-failed'); + return; + } + if (matches.length > 1) { + core.setFailed(`Ambiguous PR resolution: ${matches.length} open Dependabot PRs match SHA ${run.head_sha}`); + return; + } + const { data: full } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: matches[0].number, + }); + pr = full; + } else { + // Hydrate the full PR object so fields like `body` and `draft` are reliable. + const { data: full } = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: pr.number, + }); + pr = full; + } + if (pr.user.login !== 'dependabot[bot]') { core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'not-dependabot'); return; @@ -82,6 +121,7 @@ steps: core.exportVariable('PR_DEPENDABOT_SKIP_REASON', 'draft'); return; } + core.exportVariable('PR_NUMBER', String(pr.number)); core.exportVariable('PR_TITLE', pr.title); core.exportVariable('PR_HEAD_REF', pr.head.ref); @@ -89,31 +129,35 @@ steps: core.exportVariable('PR_AUTHOR', pr.user.login); core.exportVariable('PR_HEAD_SHA', pr.head.sha); - // Look up the most recent PR Validation workflow run for this head SHA. - // It may still be in progress when pull_request_target fires. - let conclusion = 'pending'; - let runUrl = ''; + // PR Validation conclusion comes directly from the triggering workflow_run payload; + // it is always final under `types: [completed]`. + core.exportVariable('PR_VALIDATION_CONCLUSION', run.conclusion); + core.exportVariable('PR_VALIDATION_RUN_URL', run.html_url || ''); + + // Resolve per-surface check-runs ONCE here so the persona does not re-walk them. + // Paginate to avoid silently missing checks when the matrix grows beyond a single page. + let failing = []; try { - const { data } = await github.rest.actions.listWorkflowRunsForRepo({ + const checkRuns = await github.paginate(github.rest.checks.listForRef, { owner: context.repo.owner, repo: context.repo.repo, - head_sha: pr.head.sha, - event: 'pull_request', - per_page: 30, - }); - const validation = (data.workflow_runs || []).find(r => r.name === 'PR Validation'); - if (validation) { - runUrl = validation.html_url || ''; - conclusion = validation.status === 'completed' - ? (validation.conclusion || 'unknown') - : `in_progress:${validation.status}`; - } + ref: pr.head.sha, + per_page: 100, + }, response => response.data.check_runs); + failing = checkRuns + .filter(c => c.status === 'completed' + && !['success', 'neutral', 'skipped'].includes(c.conclusion)) + .map(c => ({ name: c.name, html_url: c.html_url, conclusion: c.conclusion })); } catch (err) { - core.warning(`Failed to look up PR Validation run: ${err.message}`); + core.warning(`Failed to enumerate check-runs: ${err.message}`); } - core.exportVariable('PR_VALIDATION_CONCLUSION', conclusion); - core.exportVariable('PR_VALIDATION_RUN_URL', runUrl); - core.info(`Resolved PR #${pr.number} (${pr.title}); PR Validation conclusion: ${conclusion}`); + core.exportVariable('PR_VALIDATION_FAILING_CHECKS', JSON.stringify(failing)); + + // Hydrate Dependabot enrichment input from REST so the agent does not depend on + // the integrity-filtered MCP read of the PR body. + core.exportVariable('PR_BODY', pr.body || ''); + + core.info(`Resolved PR #${pr.number} (${pr.title}); PR Validation conclusion: ${run.conclusion}; failing checks: ${failing.length}`); tools: github: toolsets: [context, repos, pull_requests] @@ -131,11 +175,13 @@ tools: safe-outputs: create-pull-request-review-comment: max: 5 + target: "*" submit-pull-request-review: max: 1 + target: ${{ env.PR_NUMBER }} add-comment: max: 2 - target: triggering + target: ${{ env.PR_NUMBER }} noop: max: 1 imports: @@ -148,20 +194,38 @@ Advisory-only review of Dependabot-authored pull requests in microsoft/physical- ## Trigger Posture -This workflow runs via `pull_request_target` on Dependabot PRs targeting `main` (head branch matching `dependabot/**`). -It executes in the base-repository context so safe-output handlers receive a real `github.event.pull_request` payload -and can post inline review comments and a single submitted review. The `PR Validation` orchestrator runs in parallel — -its conclusion is queried at runtime by the resolver step and may be `pending` if the orchestrator has not finished yet. -The agent must never attempt to run validation tooling (`uv`, `pytest`, `npm ci`, `terraform`, `go`) from the bash tool -because those binaries are not visible inside the AWF firewall sandbox. +This workflow runs via `workflow_run` after the `PR Validation` orchestrator completes on a Dependabot +PR's head branch (`dependabot/**`) for a `pull_request` event. The `branches:` filter on `workflow_run` +matches the *triggering run's `head_branch`*, not its base — using `main` here would silently never fire +for Dependabot PRs (regression observed in #583, fixed in #584; do not change without re-reading those). +Because `workflow_run` evaluates the workflow file from the default branch, the +agent step always uses the trusted, merged definition rather than fork content. The gh-aw compiler +auto-injects fork-PR exclusion and a `repository.id` guard into the lock file. The workflow-level +`if:` short-circuits any non-PR triggering event, any PR not authored by `dependabot[bot]` (gated on +`workflow_run.actor.login`), and any non-terminal conclusion before the resolver runs. The resolver then +reads the orchestrator's terminal conclusion directly from `context.payload.workflow_run.conclusion`, +which under `types: [completed]` is always one of `success`, `failure`, `cancelled`, `timed_out`, +`neutral`, `skipped`, or `action_required`. + +The resolver step exports `PR Validation`'s final conclusion directly from +`context.payload.workflow_run.conclusion` (no separate `listWorkflowRunsForRepo` call), then enumerates +per-surface check-runs once via `checks.listForRef` so the agent never has to walk the checks API itself. +The `checks: read` permission grants exactly that scope and nothing more. The agent runs without a +working tree — all PR context comes from REST APIs in the resolver. Do not add a checkout step; the +compiler-generated "Checkout PR branch" step in the lock file is permanently skipped under +`workflow_run` because neither `github.event.pull_request` nor `github.event.issue.pull_request` is set. +The agent must never attempt to run validation tooling (`uv`, `pytest`, `npm ci`, `terraform`, `go`) +from the bash tool because those binaries are not visible inside the AWF firewall sandbox. The resolver step exports these environment variables for the agent to read: * `PR_NUMBER` — the Dependabot PR number under review * `PR_TITLE`, `PR_HEAD_REF`, `PR_BASE_REF`, `PR_AUTHOR`, `PR_HEAD_SHA` -* `PR_VALIDATION_CONCLUSION` — `pending`, `in_progress:`, `success`, `failure`, `cancelled`, `neutral`, `skipped`, `timed_out`, `action_required`, or `unknown` -* `PR_VALIDATION_RUN_URL` — direct link to the `PR Validation` run, or empty when no run exists yet -* `PR_DEPENDABOT_SKIP_REASON` (optional) — set when the resolver determined the trigger should be skipped (`not-dependabot`, `draft`) +* `PR_VALIDATION_CONCLUSION` — final terminal conclusion: `success`, `failure`, `cancelled`, `timed_out`, `neutral`, `skipped`, `action_required`, or `unknown` +* `PR_VALIDATION_RUN_URL` — direct link to the `PR Validation` run +* `PR_VALIDATION_FAILING_CHECKS` — JSON array of `{name, html_url, conclusion}` for non-success/non-neutral/non-skipped check-runs on `PR_HEAD_SHA` +* `PR_BODY` — the PR body, hydrated server-side so enrichment does not depend on the integrity-filtered MCP read +* `PR_DEPENDABOT_SKIP_REASON` (optional) — set when the resolver determined the trigger should be skipped (`not-a-pr-run`, `pr-resolution-failed`, `not-dependabot`, `draft`) When `PR_DEPENDABOT_SKIP_REASON` is set, emit a `noop` with the reason as the rationale and stop. @@ -185,13 +249,13 @@ The full reviewer persona, risk rubric, ecosystem-specific checks, and enrichmen ## Step-by-Step -1. **Resolve context.** Read `PR_NUMBER`, `PR_HEAD_SHA`, `PR_VALIDATION_CONCLUSION`, and `PR_VALIDATION_RUN_URL` from the environment. If `PR_DEPENDABOT_SKIP_REASON` is set, emit `noop` and stop. -2. **Read CI signal.** Use the `github` MCP `pull_requests` toolset (or `GET /repos/{owner}/{repo}/commits/{sha}/check-runs`) on `PR_HEAD_SHA` to enumerate per-surface check-run conclusions. Map them through the surface table in the persona. -3. **Parse.** Read the pull request title, body, and file diff. Extract package name, ecosystem, old/new versions, `GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}` and `CVE-\d{4}-\d{4,7}` identifiers from the Dependabot body. +1. **Resolve context.** Read `PR_NUMBER`, `PR_HEAD_SHA`, `PR_VALIDATION_CONCLUSION`, `PR_VALIDATION_RUN_URL`, `PR_VALIDATION_FAILING_CHECKS`, and `PR_BODY` from the environment. If `PR_DEPENDABOT_SKIP_REASON` is set, emit `noop` and stop. +2. **Read CI signal.** Treat `PR_VALIDATION_CONCLUSION` as the final, non-stale conclusion. Parse `PR_VALIDATION_FAILING_CHECKS` (JSON) for the list of failing per-surface check-runs. Do NOT call `checks.listForRef` or `commits/{sha}/check-runs` — the resolver already did. +3. **Parse.** Read `PR_BODY` plus the file diff. Extract package name, ecosystem, old/new versions, `GHSA-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}` and `CVE-\d{4}-\d{4,7}` identifiers from the Dependabot body. 4. **Enrich.** Query GHSA (preferred), fall back to OSV (`api.osv.dev`) and NVD (`services.nvd.nist.gov`) for severity, affected ranges, and fixed versions. Fetch release notes or changelog via the relevant package registry (npm, PyPI, Go module proxy, Terraform registry). 5. **Classify.** Apply the persona's per-surface rubric. Flag ABI-sensitive pins (for example `numpy >=1.26.0,<2.0.0` in Isaac Sim training), pre-1.0 bumps, major version jumps, and missing upstream advisories. -6. **Review.** Post up to five inline `create-pull-request-review-comment` entries for specific risks, up to two `add-comment` status updates on the triggering PR, and exactly one `submit-pull-request-review` with `APPROVE` or `COMMENT`. - When `PR_VALIDATION_CONCLUSION` is anything other than `success`, the verdict MUST be `COMMENT` and the body MUST quote the failing per-surface check-run names plus their `html_url`. +6. **Review.** Post up to five inline `create-pull-request-review-comment` entries for specific risks, up to two `add-comment` status updates on the resolved PR, and exactly one `submit-pull-request-review` with `APPROVE` or `COMMENT`. + When `PR_VALIDATION_CONCLUSION` is anything other than `success`, the verdict MUST be `COMMENT` and the body MUST quote each entry from `PR_VALIDATION_FAILING_CHECKS` (`name` plus `html_url`). Never skip enrichment on red CI — maintainers rely on advisory output to triage which package in a grouped PR caused the failure. Keep comments factual and concise. Cite the advisory identifier, affected versions, and the Dependabot PR URL. diff --git a/scripts/linting/Invoke-YamlLint.ps1 b/scripts/linting/Invoke-YamlLint.ps1 index 62349642..8c50aa92 100644 --- a/scripts/linting/Invoke-YamlLint.ps1 +++ b/scripts/linting/Invoke-YamlLint.ps1 @@ -68,18 +68,21 @@ function Invoke-YamlLintCore { Write-Host "Linting changed workflow files only (base: $BaseBranch)" $allChanged = @(Get-ChangedFilesFromGit -BaseBranch $BaseBranch -FileExtensions @('*.yml', '*.yaml')) $filesToLint = @($allChanged | Where-Object { - $_ -match '\.github[\\/]workflows[\\/]' -and $_ -notmatch '\.lock\.yml$' - }) + $_ -match '\.github[\\/]workflows[\\/]' + }) } else { Write-Host 'Linting all GitHub Actions workflow files' $workflowDir = Join-Path $repoRoot '.github/workflows' if (Test-Path $workflowDir) { - $filesToLint = @(Get-ChildItem -Path $workflowDir -Recurse -Include '*.yml', '*.yaml' -Exclude '*.lock.yml' -File | + $filesToLint = @(Get-ChildItem -Path $workflowDir -Recurse -Include '*.yml', '*.yaml' -File | Select-Object -ExpandProperty FullName) } } + # Lock files are gh-aw machine-generated; never lint them. + $filesToLint = @($filesToLint | Where-Object { $_ -notmatch '\.lock\.(yml|yaml)$' }) + Write-Host "Found $(@($filesToLint).Count) workflow file(s) to lint" if (@($filesToLint).Count -gt 0) { $filesToLint | ForEach-Object { Write-Host " - $_" }