From e8624eb2bdc1a20cd0b2f8b62206b1d927ff1497 Mon Sep 17 00:00:00 2001 From: Jonathan Zhang Date: Mon, 4 May 2026 22:04:49 -0700 Subject: [PATCH] fix(claude-automerge): check bypass label BEFORE path-scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Option-A bypass label (`auto-merge-approved`) was unreachable for PRs whose diff exceeded GitHub's 20k-line `gh pr diff` cap. Old order: 1. detect Claude authorship 2. check classifier_verdict label (risk:blocked) 3. risk-tier path-scan ← `gh pr diff` HTTP 406 on >20k-line PRs; `set -euo pipefail` exits step with code 1 4. check Option-A bypass label (never reached) 5. check Option-B Codex bypass (never reached) 6. enable auto-merge Once step 3 exited 1, the workflow halted with no path to recovery — applying the `auto-merge-approved` label triggered a re-run that hit the same wall. The bypass mechanism the policy advertises was inert on exactly the class of PR most likely to need it (large data drops, fixture refreshes, generated-code commits). Verified live against topcoder1/attaxion_dev#71 (2026-05-04, 261k-line Tokio Stage C data drop): classify / Classify PR Risk → HTTP 406, exit 1 automerge / automerge → HTTP 406, exit 1 Bypass label applied → workflow re-ran, same exit 1 Repo admin had to admin-override-merge through the GitHub UI. Fix: move the Option-A bypass label check UP, before the path-scan, and gate the path-scan on `bypass != '1'`. New order: 1. detect Claude authorship 2. check classifier_verdict label 3. **check Option-A bypass label** ← new position 4. risk-tier path-scan, gated on `bypass != '1'` so oversized PRs skip the diff fetch entirely 5. check Option-B Codex bypass (only when path-scan ran + risky) 6. enable auto-merge Behavior on each scenario: Claude PR + classifier:blocked → blocked=1; bypass_label SKIPPED; risk SKIPPED; revoke runs; comment-when-classifier-blocked runs. (Unchanged.) Claude PR + auto-merge-approved label → blocked=0; bypass_label runs (bypass=1); risk SKIPPED; bypass_codex SKIPPED; auto-merge runs. (NEW: works for >20k-line PRs that previously crashed.) Claude PR + non-risky paths + no bypass label → blocked=0; bypass_label runs (bypass=0); risk runs (risky=0); bypass_codex SKIPPED; auto-merge runs. (Unchanged.) Claude PR + risky paths + no bypass label → blocked=0; bypass_label runs (bypass=0); risk runs (risky=1); bypass_codex runs (Codex pass → auto-merge; otherwise comment). (Unchanged.) Claude PR + risky paths + bypass label → blocked=0; bypass_label runs (bypass=1); risk SKIPPED; bypass_codex SKIPPED; auto-merge runs. (Functionally equivalent to old behavior; minor UX change — path-scan output no longer appears in the run log when bypass is applied. The label is the audit trail; what was overridden is implicit.) Verified: - actionlint clean - python yaml.safe_load parses - all 5 scenarios traced through every downstream step's `if:` condition; no breakage Auto-merge rationale: Reusable workflow change. Touches only step ordering + one if-condition. Fail-closed default preserved (any error in bypass_label step exits 1 via `set -euo pipefail` before risk runs, same as before). All callers in the fleet (33 repos) get the fix automatically on next workflow_call. No caller-side changes needed. Refs: topcoder1/attaxion_dev#71 (the live failure case), global CLAUDE.md auto-merge policy block. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/claude-author-automerge.yml | 66 +++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/.github/workflows/claude-author-automerge.yml b/.github/workflows/claude-author-automerge.yml index ec96722..829fd64 100644 --- a/.github/workflows/claude-author-automerge.yml +++ b/.github/workflows/claude-author-automerge.yml @@ -191,11 +191,50 @@ jobs: exit 1 fi + # Option A: explicit human approval via label. + # Run BEFORE the risk-tier path-scan so an oversized PR (>20k diff + # lines, GitHub's `gh pr diff` HTTP 406 cap) can still bypass-merge. + # Without this ordering, the path-scan dies on the diff fetch BEFORE + # ever consulting the bypass label, leaving the workflow exit-1 + # with no way for the human to override. Verified against + # topcoder1/attaxion_dev#71 (2026-05-04, 261k-line data drop) where + # `auto-merge-approved` was applied but the workflow died at the + # diff-fetch step regardless. + # + # When a Claude PR matches the risk-tier classifier (or the + # path-scan can't run), you can apply the `auto-merge-approved` + # label from the PR list page (one click, no PR detail navigation) + # to bypass both the risk gate AND the size-cap failure mode. + # Confirms "yes I read it, auto-merge it" without requiring the + # actual click-merge round-trip. + - name: Check risk bypass label (Option A) + id: bypass_label + if: | + steps.detect.outputs.claude_authored == '1' && + steps.classifier_verdict.outputs.blocked != '1' + env: + BYPASS_LABEL: ${{ inputs.risk_bypass_label }} + LABELS_JSON: ${{ toJson(github.event.pull_request.labels) }} + run: | + set -euo pipefail + if [ -z "$BYPASS_LABEL" ]; then + echo "bypass=0" >> "$GITHUB_OUTPUT" + echo "Bypass label disabled (input empty)." + exit 0 + fi + if echo "$LABELS_JSON" | jq -e --arg L "$BYPASS_LABEL" '.[] | select(.name == $L)' >/dev/null 2>&1; then + echo "bypass=1" >> "$GITHUB_OUTPUT" + echo "Bypass label '$BYPASS_LABEL' present — risk-tier gate overridden (path-scan will be skipped)." + else + echo "bypass=0" >> "$GITHUB_OUTPUT" + fi + - name: Check risk-tier paths id: risk if: | steps.detect.outputs.claude_authored == '1' && - steps.classifier_verdict.outputs.blocked != '1' + steps.classifier_verdict.outputs.blocked != '1' && + steps.bypass_label.outputs.bypass != '1' env: GH_TOKEN: ${{ github.token }} PR: ${{ github.event.pull_request.number }} @@ -253,31 +292,6 @@ jobs: echo "No risk-tier paths matched." fi - # Option A: explicit human approval via label. - # When a Claude PR matches the risk-tier classifier, you can apply the - # `auto-merge-approved` label from the PR list page (one click, no PR - # detail navigation) to bypass the risk gate. Confirms "yes I read it, - # auto-merge it" without requiring the actual click-merge round-trip. - - name: Check risk bypass label (Option A) - id: bypass_label - if: steps.detect.outputs.claude_authored == '1' && steps.risk.outputs.risky == '1' - env: - BYPASS_LABEL: ${{ inputs.risk_bypass_label }} - LABELS_JSON: ${{ toJson(github.event.pull_request.labels) }} - run: | - set -euo pipefail - if [ -z "$BYPASS_LABEL" ]; then - echo "bypass=0" >> "$GITHUB_OUTPUT" - echo "Bypass label disabled (input empty)." - exit 0 - fi - if echo "$LABELS_JSON" | jq -e --arg L "$BYPASS_LABEL" '.[] | select(.name == $L)' >/dev/null 2>&1; then - echo "bypass=1" >> "$GITHUB_OUTPUT" - echo "Bypass label '$BYPASS_LABEL' present — risk-tier gate overridden." - else - echo "bypass=0" >> "$GITHUB_OUTPUT" - fi - # Option B: Codex review trust. # When the configured Codex status check is SUCCESS, treat the PR as # second-model-reviewed and bypass the risk-tier gate. The check name