From fa639dd7f69b62fb3e434c7aee4d25760f9659e7 Mon Sep 17 00:00:00 2001 From: Adam Scerra Date: Wed, 13 May 2026 22:24:43 -0400 Subject: [PATCH] feat: disable fix agent auto-run on human-authored PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a human submits a PR and the review agent requests changes, the fix agent no longer auto-triggers. This is now the default behavior for human-authored PRs. Bot-authored PRs (from the code agent) continue to auto-trigger fixes as before. Humans can opt in to the review-fix loop by adding the `fullsend-fix` label to their PR. The existing `/fix` command for manual invocation is unaffected. Changes across all dispatch paths (org-level shim, reusable dispatcher, per-repo scaffold): - Gate fix dispatch on PR author ending in [bot] OR presence of `fullsend-fix` label - Add defense-in-depth check in reusable-fix.yml that verifies PR author and label before running the fix agent E2E tested in ascerra-fullsend-lab org (human-pr-fix-test repo): 1. Human PR, no label (PR #1) — fix agent SKIPPED ✅ - Review: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25836235094 - Shim skipped dispatch-fix-bot: https://github.com/ascerra-fullsend-lab/human-pr-fix-test/actions/runs/25836370170 2. Human PR, fullsend-fix label (PR #2) — fix agent auto-triggered ✅ - Review: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25837485822 - Shim dispatched fix: https://github.com/ascerra-fullsend-lab/human-pr-fix-test/actions/runs/25837620440 - Fix agent succeeded: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25837623950 3. Bot PR, no label (PR #4, author: ascerra-fullsend-lab-coder[bot]) — fix agent auto-triggered ✅ - Code agent created PR: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25838129538 - Review requested changes: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25838396492 - Shim dispatched fix (no label needed): https://github.com/ascerra-fullsend-lab/human-pr-fix-test/actions/runs/25838580565 - Fix agent succeeded: https://github.com/ascerra-fullsend-lab/.fullsend/actions/runs/25838584099 Co-authored-by: Cursor --- .github/workflows/fullsend.yaml | 4 ++++ .github/workflows/reusable-dispatch.yml | 7 +++++-- .github/workflows/reusable-fix.yml | 18 +++++++++++++++--- .../.github/workflows/dispatch.yml | 9 ++++++--- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/.github/workflows/fullsend.yaml b/.github/workflows/fullsend.yaml index 3e2dba45f..ae036bcf8 100644 --- a/.github/workflows/fullsend.yaml +++ b/.github/workflows/fullsend.yaml @@ -202,6 +202,10 @@ jobs: && github.event.review.user.login == format('{0}-review[bot]', github.repository_owner) && github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name && !contains(github.event.pull_request.labels.*.name, 'fullsend-no-fix') + && ( + endsWith(github.event.pull_request.user.login, '[bot]') + || contains(github.event.pull_request.labels.*.name, 'fullsend-fix') + ) steps: - name: Build minimal payload id: payload diff --git a/.github/workflows/reusable-dispatch.yml b/.github/workflows/reusable-dispatch.yml index 553225836..a77e7d136 100644 --- a/.github/workflows/reusable-dispatch.yml +++ b/.github/workflows/reusable-dispatch.yml @@ -78,6 +78,7 @@ jobs: TRIGGERING_LABEL: ${{ github.event.label.name }} PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} + PR_USER_LOGIN: ${{ github.event.pull_request.user.login }} ORG_NAME: ${{ github.repository_owner }} run: | set -euo pipefail @@ -185,8 +186,10 @@ jobs: if [[ -n "${PR_HEAD_REPO}" && -n "${PR_BASE_REPO}" ]]; then if [[ "${PR_HEAD_REPO}" == "${PR_BASE_REPO}" ]]; then if ! has_label "fullsend-no-fix" "${PR_LABELS}"; then - STAGE="fix" - TRIGGER_SOURCE="${REVIEW_USER_LOGIN}" + if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || has_label "fullsend-fix" "${PR_LABELS}"; then + STAGE="fix" + TRIGGER_SOURCE="${REVIEW_USER_LOGIN}" + fi fi fi fi diff --git a/.github/workflows/reusable-fix.yml b/.github/workflows/reusable-fix.yml index a21a3af9e..1bf6054e7 100644 --- a/.github/workflows/reusable-fix.yml +++ b/.github/workflows/reusable-fix.yml @@ -238,7 +238,7 @@ jobs: echo "Fix iteration: ${ITERATION} (${FIX_COMMITS} previous fix commits)" >&2 echo "iteration=${ITERATION}" >> "${GITHUB_OUTPUT}" - - name: Check fullsend-no-fix label + - name: Check fix eligibility env: GH_TOKEN: ${{ steps.app-token.outputs.token }} TRIGGER_SOURCE: ${{ inputs.trigger_source }} @@ -246,12 +246,24 @@ jobs: SOURCE_REPO: ${{ inputs.source_repo }} run: | if [[ "${TRIGGER_SOURCE}" =~ \[bot\]$ ]]; then - HAS_NO_FIX=$(gh pr view "${PR_NUM}" --repo "${SOURCE_REPO}" \ - --json labels --jq '[.labels[].name] | any(. == "fullsend-no-fix")' 2>/dev/null || echo "false") + PR_INFO=$(gh pr view "${PR_NUM}" --repo "${SOURCE_REPO}" \ + --json labels,author --jq '{labels: [.labels[].name], author: .author.login}' 2>/dev/null \ + || echo '{"labels":[],"author":""}') + + HAS_NO_FIX=$(echo "${PR_INFO}" | jq -r '.labels | any(. == "fullsend-no-fix")') if [[ "${HAS_NO_FIX}" == "true" ]]; then echo "::warning::PR #${PR_NUM} has 'fullsend-no-fix' label — skipping bot-triggered fix" exit 1 fi + + PR_AUTHOR=$(echo "${PR_INFO}" | jq -r '.author') + if [[ ! "${PR_AUTHOR}" =~ \[bot\]$ ]]; then + HAS_FIX_LABEL=$(echo "${PR_INFO}" | jq -r '.labels | any(. == "fullsend-fix")') + if [[ "${HAS_FIX_LABEL}" != "true" ]]; then + echo "::warning::Human-authored PR #${PR_NUM} without 'fullsend-fix' label — skipping bot-triggered fix" + exit 1 + fi + fi fi - name: Checkout target repository at PR HEAD diff --git a/internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml b/internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml index 0d5d1301c..09cfad7ae 100644 --- a/internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml +++ b/internal/scaffold/fullsend-repo/.github/workflows/dispatch.yml @@ -1,4 +1,4 @@ -# lint-workflow-size: max-lines=380 +# lint-workflow-size: max-lines=385 # Dispatcher workflow that routes events to agent workflows based on stage. # Routing logic determines the stage from event context — the shim only # forwards the raw event. Adding a new stage requires only a case branch @@ -41,6 +41,7 @@ jobs: TRIGGERING_LABEL: ${{ github.event.label.name }} PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} PR_BASE_REPO: ${{ github.event.pull_request.base.repo.full_name }} + PR_USER_LOGIN: ${{ github.event.pull_request.user.login }} ORG_NAME: ${{ github.repository_owner }} run: | set -euo pipefail @@ -165,8 +166,10 @@ jobs: # Check no-fix label (use PR_LABELS — issue.labels is empty # on pull_request_review events) if ! has_label "fullsend-no-fix" "${PR_LABELS}"; then - STAGE="fix" - TRIGGER_SOURCE="${REVIEW_USER_LOGIN}" + if [[ "${PR_USER_LOGIN}" =~ \[bot\]$ ]] || has_label "fullsend-fix" "${PR_LABELS}"; then + STAGE="fix" + TRIGGER_SOURCE="${REVIEW_USER_LOGIN}" + fi fi fi fi