diff --git a/.github/workflows/evergreen.lock.yml b/.github/workflows/evergreen.lock.yml index 391256fd..4651ab07 100644 --- a/.github/workflows/evergreen.lock.yml +++ b/.github/workflows/evergreen.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"27a9a3244def67a4c930ff6abe6b787bef8dad3d65bf0a7cdd3421ac0bc2300b","body_hash":"0db8335b57a7858a2eab1dcaea3a2b399463c65a12cad7f87ace682522b9e696","compiler_version":"v0.79.4","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"cf206b5b05f7478a7e228b21d83a4d2260b702d968a30ceca5d2fed9f7022725","body_hash":"0db8335b57a7858a2eab1dcaea3a2b399463c65a12cad7f87ace682522b9e696","compiler_version":"v0.79.4","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_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":"d059700c6a8ec3b5fd798b9ea60f5d048447b918","version":"v0.79.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.0"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.0"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.0"},{"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"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} # ___ _ _ # / _ \ | | (_) @@ -1681,7 +1681,7 @@ jobs: checks: read contents: read issues: write - pull-requests: read + pull-requests: write statuses: read concurrency: @@ -1738,6 +1738,14 @@ jobs: jq -e --arg label "$label" '[.labels[].name] | index($label) != null' <<<"$payload" >/dev/null } + edit_pr_label() { + local pr="$1" + local flag="$2" + local label="$3" + + gh issue edit "$pr" --repo "$REPO" "$flag" "$label" + } + ensure_active_label() { gh label create "$ACTIVE_LABEL" --repo "$REPO" \ --color "fbca04" \ @@ -1865,13 +1873,42 @@ jobs: reconcile_ready_label() { local pr="$1" local state="$2" + local payload="$3" if [ "$state" = "ready" ]; then - gh issue edit "$pr" --repo "$REPO" --add-label "$READY_LABEL" || true + if pr_has_label "$payload" "$READY_LABEL"; then + return 0 + fi + edit_pr_label "$pr" --add-label "$READY_LABEL" return 0 fi - gh issue edit "$pr" --repo "$REPO" --remove-label "$READY_LABEL" || true + if pr_has_label "$payload" "$READY_LABEL"; then + edit_pr_label "$pr" --remove-label "$READY_LABEL" + fi + return 0 + } + + claim_active_label() { + local pr="$1" + local head_sha="$2" + local reason="$3" + local payload + + ensure_active_label + if ! edit_pr_label "$pr" --add-label "$ACTIVE_LABEL"; then + echo "Could not add $ACTIVE_LABEL to PR #$pr; refusing to dispatch the agent without a lease." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:active_label_failed" + return 1 + fi + + payload="$(pr_json "$pr")" + if ! pr_has_label "$payload" "$ACTIVE_LABEL"; then + echo "PR #$pr still does not have $ACTIVE_LABEL after label update; refusing to dispatch the agent." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:active_label_missing" + return 1 + fi + return 0 } trigger_ci_if_needed() { @@ -1964,7 +2001,11 @@ jobs: fi state="$(evaluate_readiness "$payload")" - reconcile_ready_label "$pr" "$state" + if ! reconcile_ready_label "$pr" "$state" "$payload"; then + echo "Could not reconcile $READY_LABEL for PR #$pr; refusing to dispatch the agent with stale readiness state." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:ready_label_failed" + return 1 + fi case "$state" in ready|waiting|blocked|out_of_scope) @@ -1976,8 +2017,9 @@ jobs: return 1 ;; needs_branch_update|needs_repair) - ensure_active_label - gh issue edit "$pr" --repo "$REPO" --add-label "$ACTIVE_LABEL" + if ! claim_active_label "$pr" "$head_sha" "$reason"; then + return 1 + fi set_result "true" "$pr" "$head_sha" "$state" "$reason:$state" return 0 ;; diff --git a/.github/workflows/evergreen.md b/.github/workflows/evergreen.md index 0b67832e..de12715e 100644 --- a/.github/workflows/evergreen.md +++ b/.github/workflows/evergreen.md @@ -42,7 +42,7 @@ jobs: checks: read contents: read issues: write - pull-requests: read + pull-requests: write statuses: read outputs: should_run: ${{ steps.evaluate.outputs.should_run }} @@ -101,6 +101,14 @@ jobs: jq -e --arg label "$label" '[.labels[].name] | index($label) != null' <<<"$payload" >/dev/null } + edit_pr_label() { + local pr="$1" + local flag="$2" + local label="$3" + + gh issue edit "$pr" --repo "$REPO" "$flag" "$label" + } + ensure_active_label() { gh label create "$ACTIVE_LABEL" --repo "$REPO" \ --color "fbca04" \ @@ -228,13 +236,42 @@ jobs: reconcile_ready_label() { local pr="$1" local state="$2" + local payload="$3" if [ "$state" = "ready" ]; then - gh issue edit "$pr" --repo "$REPO" --add-label "$READY_LABEL" || true + if pr_has_label "$payload" "$READY_LABEL"; then + return 0 + fi + edit_pr_label "$pr" --add-label "$READY_LABEL" return 0 fi - gh issue edit "$pr" --repo "$REPO" --remove-label "$READY_LABEL" || true + if pr_has_label "$payload" "$READY_LABEL"; then + edit_pr_label "$pr" --remove-label "$READY_LABEL" + fi + return 0 + } + + claim_active_label() { + local pr="$1" + local head_sha="$2" + local reason="$3" + local payload + + ensure_active_label + if ! edit_pr_label "$pr" --add-label "$ACTIVE_LABEL"; then + echo "Could not add $ACTIVE_LABEL to PR #$pr; refusing to dispatch the agent without a lease." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:active_label_failed" + return 1 + fi + + payload="$(pr_json "$pr")" + if ! pr_has_label "$payload" "$ACTIVE_LABEL"; then + echo "PR #$pr still does not have $ACTIVE_LABEL after label update; refusing to dispatch the agent." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:active_label_missing" + return 1 + fi + return 0 } trigger_ci_if_needed() { @@ -327,7 +364,11 @@ jobs: fi state="$(evaluate_readiness "$payload")" - reconcile_ready_label "$pr" "$state" + if ! reconcile_ready_label "$pr" "$state" "$payload"; then + echo "Could not reconcile $READY_LABEL for PR #$pr; refusing to dispatch the agent with stale readiness state." + set_result "false" "$pr" "$head_sha" "blocked" "$reason:ready_label_failed" + return 1 + fi case "$state" in ready|waiting|blocked|out_of_scope) @@ -339,8 +380,9 @@ jobs: return 1 ;; needs_branch_update|needs_repair) - ensure_active_label - gh issue edit "$pr" --repo "$REPO" --add-label "$ACTIVE_LABEL" + if ! claim_active_label "$pr" "$head_sha" "$reason"; then + return 1 + fi set_result "true" "$pr" "$head_sha" "$state" "$reason:$state" return 0 ;;