From 62c7976405edfe09b533a9ffcc36321dde7dd93d Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Wed, 13 May 2026 17:32:42 +0000 Subject: [PATCH 1/5] feat(auto-rebase): add claude-rebase agentic fallback for merge conflicts When auto-rebase encounters a 422 merge conflict, it now posts a comment noting that Claude will attempt resolution automatically. The new claude-rebase job in claude-code-reusable.yml watches for that sentinel comment and runs Claude Code to check out the branch, rebase onto main, resolve conflicts (preferring newer action pins for workflow files, aborting on ambiguous application-code conflicts), push, and post a summary. Idempotency is preserved: the existing sentinel prevents the conflict comment from being re-posted, so claude-rebase fires exactly once per conflict situation. Closes #279 Co-authored-by: Don Petry --- .github/workflows/auto-rebase-reusable.yml | 11 ++- .github/workflows/claude-code-reusable.yml | 102 +++++++++++++++++++++ 2 files changed, 109 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-rebase-reusable.yml b/.github/workflows/auto-rebase-reusable.yml index 66d77baa5..46c29c9cf 100644 --- a/.github/workflows/auto-rebase-reusable.yml +++ b/.github/workflows/auto-rebase-reusable.yml @@ -22,8 +22,9 @@ # Failure modes handled gracefully: # - without `workflows` permission (403): posts an idempotent comment asking # the author to rebase manually (sentinel: ) -# - merge conflict (422): posts an idempotent comment asking the author to -# resolve conflicts (sentinel: ) +# - merge conflict (422): posts an idempotent comment noting that Claude will +# attempt an agentic rebase (sentinel: ), which +# triggers the claude-rebase job in claude-code-reusable.yml # # No secrets required — uses github.token only. No auto-merge logic. name: Auto-rebase non-Dependabot PRs (Reusable) @@ -112,8 +113,10 @@ jobs: echo " Posting conflict resolution request" CONFLICT_BODY="" CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" - CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved manually." - CONFLICT_BODY+=$'\n\n'"Please resolve the conflicts and push:" + CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." + CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" + CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." + CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" diff --git a/.github/workflows/claude-code-reusable.yml b/.github/workflows/claude-code-reusable.yml index fb9b25625..fe38f8435 100644 --- a/.github/workflows/claude-code-reusable.yml +++ b/.github/workflows/claude-code-reusable.yml @@ -346,3 +346,105 @@ jobs: - If the comment requires human judgment, leave a reply explaining what you need. 4. Check CI status. If CI fails, read the logs, fix the issues, and push again. Repeat until CI passes. 5. When CI is green, all actionable review comments are resolved, and the PR is ready, read the CODEOWNERS file and leave a comment tagging the relevant code owners to review and merge. + + # Automation mode: agentic rebase — resolve conflicts when auto-rebase fails with a merge conflict (422) + claude-rebase: + if: >- + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.user.login == 'github-actions[bot]' && + contains(github.event.comment.body, '') + concurrency: + group: claude-rebase-${{ github.event.issue.number }} + cancel-in-progress: true + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: write + id-token: write + pull-requests: write + issues: write + actions: read + checks: read + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 1 + token: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} + - name: Run Claude Code + uses: anthropics/claude-code-action@476e359e6203e73dad705c8b322e333fabbd7416 # v1.0.119 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + github_token: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} + # yamllint disable rule:line-length + claude_args: | + --allowedTools "Bash(gh pr checkout:*),Bash(gh pr view:*),Bash(gh pr comment:*),Bash(gh api:*),Bash(git*:*),Edit,Write" + # yamllint enable rule:line-length + # yamllint disable rule:line-length + prompt: | + The auto-rebase workflow detected a merge conflict on PR #${{ github.event.issue.number }}. + + Your job: check out the PR branch, rebase it onto the base branch, resolve all conflicts using best-effort judgement, push, and post a summary comment. If conflicts cannot be resolved safely, abort cleanly and post a clear failure comment with manual instructions. + + ## Steps + + ### 1. Configure git identity + ``` + git config user.name "claude[bot]" + git config user.email "claude[bot]@users.noreply.github.com" + ``` + + ### 2. Get PR details + ``` + gh pr view ${{ github.event.issue.number }} --json number,headRefName,baseRefName,title + ``` + + ### 3. Check out the PR branch + ``` + gh pr checkout ${{ github.event.issue.number }} + ``` + + ### 4. Rebase onto the base branch + ``` + git fetch origin + git rebase origin/ + ``` + (Use the actual `baseRefName` from step 2.) + + ### 5. For each conflict, apply the appropriate resolution strategy + + **Workflow YAML files (`.github/workflows/*.yml`) — action pin conflicts:** + - Read both versions (ours = PR branch, theirs = base branch). + - If the conflict is a pinned SHA/tag for a `uses:` line, compare versions and accept the newer pin. + - When the base branch has a newer or equal version, prefer the base branch (`git checkout --theirs -- `). + - When the PR branch has a provably newer pin, keep it (`git checkout --ours -- `). + - After resolving each file: `git add `, then `git rebase --continue`. + + **All other files:** + - Attempt a sensible merge of both sides if the intent is clear. + - If the conflict is complex or ambiguous, run `git rebase --abort` immediately and go to step 7b. Do not guess at application logic. + + ### 6. Push the rebased branch + ``` + git push --force-with-lease + ``` + + ### 7a. On success — post a summary comment on PR #${{ github.event.issue.number }} + Include: + - Which files had conflicts and how each was resolved. + - The new HEAD commit after rebase. + + ### 7b. On failure (rebase aborted) — post a failure comment on PR #${{ github.event.issue.number }} + Include: + - Which file(s) could not be resolved automatically and why. + - Manual resolution steps: + ``` + git fetch origin + git rebase origin/ + # resolve conflicts in + git add + git rebase --continue + git push --force-with-lease + ``` + # yamllint enable rule:line-length From 624baf84b5cb9dfe41a19ac41069e9ba47c299d0 Mon Sep 17 00:00:00 2001 From: don-petry Date: Wed, 13 May 2026 21:15:32 -0500 Subject: [PATCH 2/5] fix: address review comments on PR #281 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P1: add GH_PAT_WORKFLOWS optional secret to auto-rebase-reusable.yml workflow_call and use it for GH_TOKEN so sentinel comments are posted with a PAT, enabling issue_comment events to trigger claude-rebase - P2: fix reversed --ours/--theirs in rebase prompt (during git rebase, --ours = base branch being rebased onto, --theirs = PR branch work) - P3: require GH_PAT_WORKFLOWS for claude-rebase job (add pre-check step that fails fast if not set; remove || github.token fallback so pushes always use the PAT and trigger CI) - P4: replace ALREADY_POSTED skip with delete-and-repost strategy so a new issue_comment event always fires on repeat conflicts - P5: change fetch-depth from 1 to 0 for full history needed by rebase - P6: fetch specific base ref (git fetch origin ) rather than a bare 'git fetch origin' before rebasing - P7: update standards/ci-standards.md §8 to reflect Claude automatic rebase behavior and clarify GH_PAT_WORKFLOWS requirement Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-rebase-reusable.yml | 55 +++++++++++++--------- .github/workflows/claude-code-reusable.yml | 25 ++++++---- standards/ci-standards.md | 4 +- 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/.github/workflows/auto-rebase-reusable.yml b/.github/workflows/auto-rebase-reusable.yml index 46c29c9cf..ceedcd252 100644 --- a/.github/workflows/auto-rebase-reusable.yml +++ b/.github/workflows/auto-rebase-reusable.yml @@ -26,11 +26,19 @@ # attempt an agentic rebase (sentinel: ), which # triggers the claude-rebase job in claude-code-reusable.yml # -# No secrets required — uses github.token only. No auto-merge logic. +# Secrets: GH_PAT_WORKFLOWS is optional but required for the claude-rebase +# trigger to work. Sentinel comments posted with GITHUB_TOKEN do not fire +# issue_comment workflow runs (GitHub limitation). When the PAT is absent the +# sentinel is still posted but claude-rebase will not be scheduled. +# No auto-merge logic. name: Auto-rebase non-Dependabot PRs (Reusable) on: workflow_call: + secrets: + GH_PAT_WORKFLOWS: + description: "PAT with workflows scope — required for sentinel comment to trigger claude-rebase" + required: false jobs: auto-rebase: @@ -41,7 +49,7 @@ jobs: steps: - name: Update behind non-Dependabot PRs env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} REPO: ${{ github.repository }} run: | # Find open non-Dependabot PRs from the same repo (exclude forks) @@ -100,29 +108,32 @@ jobs: gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$BLOCKED_BODY" fi elif echo "$UPDATE_OUTPUT" | grep -qi "merge conflict"; then - # Merge conflict — ask the author to resolve it. + # Merge conflict — post the sentinel to trigger claude-rebase. # gh api surfaces the GitHub API error JSON: {"message":"merge conflict",...} # - # Idempotent: skip if sentinel comment already exists. + # Re-trigger strategy: if a prior sentinel exists (e.g. Claude + # previously aborted and main was pushed again), delete it and + # re-post so a new issue_comment event fires and claude-rebase + # is scheduled for this fresh conflict. SENTINEL="" - ALREADY_POSTED=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ - --json comments --jq "[.comments[] | select(.body | contains(\"$SENTINEL\"))] | length") - if [[ "$ALREADY_POSTED" -gt 0 ]]; then - echo " Skipping — conflict comment already posted" - else - echo " Posting conflict resolution request" - CONFLICT_BODY="" - CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" - CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." - CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" - CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." - CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" - CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" - CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" - CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" - CONFLICT_BODY+=$'\n'"git add ."$'\n'"git commit"$'\n'"git push"$'\n'"\`\`\`" - gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$CONFLICT_BODY" - fi + OLD_COMMENT_IDS=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ + --json comments --jq "[.comments[] | select(.body | contains(\"$SENTINEL\")) | .databaseId] | .[]") + for OLD_ID in $OLD_COMMENT_IDS; do + echo " Deleting stale sentinel comment $OLD_ID" + gh api "repos/$REPO/issues/comments/$OLD_ID" -X DELETE || true + done + echo " Posting conflict resolution request" + CONFLICT_BODY="" + CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" + CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." + CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" + CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." + CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" + CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" + CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" + CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" + CONFLICT_BODY+=$'\n'"git add ."$'\n'"git commit"$'\n'"git push"$'\n'"\`\`\`" + gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$CONFLICT_BODY" else echo " Warning: failed to update PR #$PR_NUMBER — $UPDATE_OUTPUT" fi diff --git a/.github/workflows/claude-code-reusable.yml b/.github/workflows/claude-code-reusable.yml index fe38f8435..5b279b27b 100644 --- a/.github/workflows/claude-code-reusable.yml +++ b/.github/workflows/claude-code-reusable.yml @@ -367,16 +367,24 @@ jobs: actions: read checks: read steps: + - name: Verify GH_PAT_WORKFLOWS is configured + env: + TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }} + run: | + if [[ -z "$TOKEN" ]]; then + echo "::error::GH_PAT_WORKFLOWS is required for claude-rebase. Pushes made with GITHUB_TOKEN do not trigger CI workflow runs (GitHub limitation), which would leave the PR without checks after rebase. Configure this secret to enable agentic rebase." + exit 1 + fi - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - fetch-depth: 1 - token: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} + fetch-depth: 0 + token: ${{ secrets.GH_PAT_WORKFLOWS }} - name: Run Claude Code uses: anthropics/claude-code-action@476e359e6203e73dad705c8b322e333fabbd7416 # v1.0.119 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - github_token: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} + github_token: ${{ secrets.GH_PAT_WORKFLOWS }} # yamllint disable rule:line-length claude_args: | --allowedTools "Bash(gh pr checkout:*),Bash(gh pr view:*),Bash(gh pr comment:*),Bash(gh api:*),Bash(git*:*),Edit,Write" @@ -407,18 +415,19 @@ jobs: ### 4. Rebase onto the base branch ``` - git fetch origin + git fetch origin git rebase origin/ ``` - (Use the actual `baseRefName` from step 2.) + (Use the actual `baseRefName` from step 2. Fetching the specific ref ensures + `origin/` is up to date even in non-default-branch scenarios.) ### 5. For each conflict, apply the appropriate resolution strategy **Workflow YAML files (`.github/workflows/*.yml`) — action pin conflicts:** - - Read both versions (ours = PR branch, theirs = base branch). + - Read both versions (ours = base branch, theirs = PR branch). - If the conflict is a pinned SHA/tag for a `uses:` line, compare versions and accept the newer pin. - - When the base branch has a newer or equal version, prefer the base branch (`git checkout --theirs -- `). - - When the PR branch has a provably newer pin, keep it (`git checkout --ours -- `). + - When the base branch has a newer or equal version, prefer the base branch (`git checkout --ours -- `). + - When the PR branch has a provably newer pin, keep it (`git checkout --theirs -- `). - After resolving each file: `git add `, then `git rebase --continue`. **All other files:** diff --git a/standards/ci-standards.md b/standards/ci-standards.md index d6ef37aaf..5735e5278 100644 --- a/standards/ci-standards.md +++ b/standards/ci-standards.md @@ -521,9 +521,9 @@ On each run the workflow: 1. Lists all open same-repo PRs excluding `dependabot[bot]` and fork PRs. 2. For each PR that is behind the base branch, calls `PUT /pulls/{n}/update-branch` with `merge` method to fast-forward it. 3. On `workflows` permission error: posts an idempotent comment (sentinel ``) asking the author to rebase manually. -4. On merge conflict (422): posts an idempotent comment (sentinel ``) asking the author to resolve conflicts. +4. On merge conflict (422): deletes any prior sentinel and posts a fresh comment (sentinel ``), which triggers the `claude-rebase` job in `claude-code-reusable.yml` to automatically resolve the conflict. If Claude cannot resolve it, it posts a clear failure comment with manual instructions. -**No secrets required** — uses `GITHUB_TOKEN` only. Dependabot PRs are excluded because `dependabot-rebase.yml` handles those. +**Secrets:** `GH_PAT_WORKFLOWS` is optional but **required for `claude-rebase` to be triggered** — comments posted with `GITHUB_TOKEN` do not fire `issue_comment` workflow runs (GitHub limitation). Without it the sentinel comment still appears but no automatic resolution will run. Dependabot PRs are excluded because `dependabot-rebase.yml` handles those. **Compliance:** The compliance audit (`check_centralized_workflow_stubs`) verifies that repos adopting `auto-rebase.yml` use the canonical thin caller stub delegating to `petry-projects/.github/.github/workflows/auto-rebase-reusable.yml@v1`. From 89c0c310ad321d2294cd4a4d18f4de6f56abb2d6 Mon Sep 17 00:00:00 2001 From: don-petry Date: Wed, 13 May 2026 21:20:57 -0500 Subject: [PATCH 3/5] =?UTF-8?q?fix(lint):=20wrap=20long=20lines=20in=20ci-?= =?UTF-8?q?standards.md=20=C2=A78?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MD013 line-length limit is 200 chars; wrap the new bullet 4 and Secrets paragraph to stay within it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- standards/ci-standards.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/standards/ci-standards.md b/standards/ci-standards.md index 5735e5278..77b492096 100644 --- a/standards/ci-standards.md +++ b/standards/ci-standards.md @@ -521,9 +521,15 @@ On each run the workflow: 1. Lists all open same-repo PRs excluding `dependabot[bot]` and fork PRs. 2. For each PR that is behind the base branch, calls `PUT /pulls/{n}/update-branch` with `merge` method to fast-forward it. 3. On `workflows` permission error: posts an idempotent comment (sentinel ``) asking the author to rebase manually. -4. On merge conflict (422): deletes any prior sentinel and posts a fresh comment (sentinel ``), which triggers the `claude-rebase` job in `claude-code-reusable.yml` to automatically resolve the conflict. If Claude cannot resolve it, it posts a clear failure comment with manual instructions. - -**Secrets:** `GH_PAT_WORKFLOWS` is optional but **required for `claude-rebase` to be triggered** — comments posted with `GITHUB_TOKEN` do not fire `issue_comment` workflow runs (GitHub limitation). Without it the sentinel comment still appears but no automatic resolution will run. Dependabot PRs are excluded because `dependabot-rebase.yml` handles those. +4. On merge conflict (422): deletes any prior sentinel and posts a fresh comment + (sentinel ``), which triggers the `claude-rebase` + job in `claude-code-reusable.yml` to automatically resolve the conflict. + If Claude cannot resolve it, it posts a clear failure comment with manual instructions. + +**Secrets:** `GH_PAT_WORKFLOWS` is optional but **required for `claude-rebase` to be triggered** — +comments posted with `GITHUB_TOKEN` do not fire `issue_comment` workflow runs (GitHub limitation). +Without it the sentinel comment still appears but no automatic resolution will run. +Dependabot PRs are excluded because `dependabot-rebase.yml` handles those. **Compliance:** The compliance audit (`check_centralized_workflow_stubs`) verifies that repos adopting `auto-rebase.yml` use the canonical thin caller stub delegating to `petry-projects/.github/.github/workflows/auto-rebase-reusable.yml@v1`. From da34df4cee452fc7fc9bfb728a67f8284b1097e4 Mon Sep 17 00:00:00 2001 From: don-petry Date: Thu, 14 May 2026 06:35:51 -0500 Subject: [PATCH 4/5] fix: address new review comments and yamllint failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove user.login == 'github-actions[bot]' check from claude-rebase trigger — PAT-authenticated comments are authored by the PAT owner, not 'github-actions[bot]', so that check always blocks the job when the PAT is configured. Rely on the sentinel text alone. - Shorten ::error:: message in Verify step to fix yamllint line-length violation (was 260 chars, now under 200). - Conditional conflict message: when GH_PAT_WORKFLOWS is unset, post a manual-only message instead of falsely promising Claude will rebase. Add HAS_PAT env var to carry the PAT-presence flag into the script. - Require GitHub API lookup (gh api .../git/refs/tags/{tag}) before choosing which action pin is newer; abort if version is unresolvable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-rebase-reusable.yml | 12 +++++++++--- .github/workflows/claude-code-reusable.yml | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-rebase-reusable.yml b/.github/workflows/auto-rebase-reusable.yml index ceedcd252..7157e7340 100644 --- a/.github/workflows/auto-rebase-reusable.yml +++ b/.github/workflows/auto-rebase-reusable.yml @@ -50,6 +50,7 @@ jobs: - name: Update behind non-Dependabot PRs env: GH_TOKEN: ${{ secrets.GH_PAT_WORKFLOWS || github.token }} + HAS_PAT: ${{ secrets.GH_PAT_WORKFLOWS != '' }} REPO: ${{ github.repository }} run: | # Find open non-Dependabot PRs from the same repo (exclude forks) @@ -126,9 +127,14 @@ jobs: CONFLICT_BODY="" CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." - CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" - CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." - CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" + if [[ "$HAS_PAT" == "true" ]]; then + CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" + CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." + CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" + else + CONFLICT_BODY+=$'\n\n'"Please resolve this manually" + CONFLICT_BODY+=" (automatic resolution requires \`GH_PAT_WORKFLOWS\` to be configured):" + fi CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" diff --git a/.github/workflows/claude-code-reusable.yml b/.github/workflows/claude-code-reusable.yml index 5b279b27b..7fd588b80 100644 --- a/.github/workflows/claude-code-reusable.yml +++ b/.github/workflows/claude-code-reusable.yml @@ -349,10 +349,12 @@ jobs: # Automation mode: agentic rebase — resolve conflicts when auto-rebase fails with a merge conflict (422) claude-rebase: + # Trigger on the sentinel comment text alone; the login check is omitted + # because when GH_PAT_WORKFLOWS is used the comment author is the PAT + # owner, not 'github-actions[bot]'. if: >- github.event_name == 'issue_comment' && github.event.issue.pull_request && - github.event.comment.user.login == 'github-actions[bot]' && contains(github.event.comment.body, '') concurrency: group: claude-rebase-${{ github.event.issue.number }} @@ -372,7 +374,7 @@ jobs: TOKEN: ${{ secrets.GH_PAT_WORKFLOWS }} run: | if [[ -z "$TOKEN" ]]; then - echo "::error::GH_PAT_WORKFLOWS is required for claude-rebase. Pushes made with GITHUB_TOKEN do not trigger CI workflow runs (GitHub limitation), which would leave the PR without checks after rebase. Configure this secret to enable agentic rebase." + echo "::error::GH_PAT_WORKFLOWS is required — GITHUB_TOKEN pushes skip CI. Set this secret to enable agentic rebase." exit 1 fi - name: Checkout repository @@ -425,9 +427,16 @@ jobs: **Workflow YAML files (`.github/workflows/*.yml`) — action pin conflicts:** - Read both versions (ours = base branch, theirs = PR branch). - - If the conflict is a pinned SHA/tag for a `uses:` line, compare versions and accept the newer pin. + - If the conflict is a pinned SHA/tag for a `uses:` line, you MUST determine + which side is newer via the GitHub API before choosing a side: + ``` + gh api repos/{owner}/{repo}/git/refs/tags/{tag} # resolve tag → SHA + gh api repos/{owner}/{repo}/git/ref/heads/{branch} # resolve branch → SHA + ``` + Compare the resolved SHAs and accept the newer pin. - When the base branch has a newer or equal version, prefer the base branch (`git checkout --ours -- `). - When the PR branch has a provably newer pin, keep it (`git checkout --theirs -- `). + - If the version cannot be determined via the API, treat it as ambiguous and abort (step 7b). - After resolving each file: `git add `, then `git rebase --continue`. **All other files:** From cd1016f2f632f30e7aa84fc95b99ae79fc416f8c Mon Sep 17 00:00:00 2001 From: don-petry Date: Thu, 14 May 2026 06:58:26 -0500 Subject: [PATCH 5/5] fix: address CodeRabbit and codex review comments (round 3) - Add author_association trust gate to claude-rebase trigger; only OWNER/MEMBER/COLLABORATOR comments with the sentinel can fire the job, preventing untrusted users from invoking a PAT-backed run. - Change sentinel to '' (embed base HEAD SHA) and skip delete+repost when the same SHA sentinel exists, preventing spurious cancellation of in-flight claude-rebase runs on active-main repos. - Switch claude-rebase concurrency to cancel-in-progress: false so a freshly-posted sentinel queues behind a running rebase rather than aborting it. - Fix API lookup paths in prompt: use canonical GET /git/ref/tags/{tag} with --jq '.object.sha' for tags and /branches/{branch} with --jq '.commit.sha' for branches. - Replace invalid SHA-based version comparison with semver comparison for tag pins and commit-date comparison (via /git/commits/{sha}) for SHA pins. - Tighten 'All other files' conflict rule: always abort on application-code conflicts; never attempt to merge by intent. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/auto-rebase-reusable.yml | 61 +++++++++++++--------- .github/workflows/claude-code-reusable.yml | 29 +++++----- 2 files changed, 52 insertions(+), 38 deletions(-) diff --git a/.github/workflows/auto-rebase-reusable.yml b/.github/workflows/auto-rebase-reusable.yml index 7157e7340..d22d0d118 100644 --- a/.github/workflows/auto-rebase-reusable.yml +++ b/.github/workflows/auto-rebase-reusable.yml @@ -112,34 +112,43 @@ jobs: # Merge conflict — post the sentinel to trigger claude-rebase. # gh api surfaces the GitHub API error JSON: {"message":"merge conflict",...} # - # Re-trigger strategy: if a prior sentinel exists (e.g. Claude - # previously aborted and main was pushed again), delete it and - # re-post so a new issue_comment event fires and claude-rebase - # is scheduled for this fresh conflict. - SENTINEL="" - OLD_COMMENT_IDS=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ - --json comments --jq "[.comments[] | select(.body | contains(\"$SENTINEL\")) | .databaseId] | .[]") - for OLD_ID in $OLD_COMMENT_IDS; do - echo " Deleting stale sentinel comment $OLD_ID" - gh api "repos/$REPO/issues/comments/$OLD_ID" -X DELETE || true - done - echo " Posting conflict resolution request" - CONFLICT_BODY="" - CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" - CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." - if [[ "$HAS_PAT" == "true" ]]; then - CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" - CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." - CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" + # Re-trigger strategy: embed the current BASE_BRANCH HEAD SHA in + # the sentinel so we can detect whether the base has moved. If an + # existing sentinel already carries the same SHA, skip delete+repost + # (avoiding cancellation of an in-flight claude-rebase run). Only + # delete and re-post when the base HEAD has changed (new conflict). + BASE_SHA=$(gh api "repos/$REPO/git/ref/heads/$BASE_BRANCH" --jq '.object.sha') + SENTINEL_PREFIX="" + SAME_SHA_COUNT=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ + --json comments --jq "[.comments[] | select(.body | contains(\"$SENTINEL\")) | .databaseId] | length") + if [[ "$SAME_SHA_COUNT" -gt 0 ]]; then + echo " Sentinel already posted for base SHA $BASE_SHA, skipping to avoid cancelling active rebase" else - CONFLICT_BODY+=$'\n\n'"Please resolve this manually" - CONFLICT_BODY+=" (automatic resolution requires \`GH_PAT_WORKFLOWS\` to be configured):" + OLD_COMMENT_IDS=$(gh pr view "$PR_NUMBER" --repo "$REPO" \ + --json comments --jq "[.comments[] | select(.body | contains(\"$SENTINEL_PREFIX\")) | .databaseId] | .[]") + for OLD_ID in $OLD_COMMENT_IDS; do + echo " Deleting stale sentinel comment $OLD_ID" + gh api "repos/$REPO/issues/comments/$OLD_ID" -X DELETE || true + done + echo " Posting conflict resolution request" + CONFLICT_BODY="$SENTINEL" + CONFLICT_BODY+=$'\n'"**Auto-rebase failed — merge conflict** — this branch has conflicts" + CONFLICT_BODY+=" with \`$BASE_BRANCH\` that must be resolved." + if [[ "$HAS_PAT" == "true" ]]; then + CONFLICT_BODY+=$'\n\n'"Claude will attempt to resolve this automatically. If it cannot, a" + CONFLICT_BODY+=" follow-up comment will explain what needs manual attention." + CONFLICT_BODY+=$'\n\n'"To resolve manually instead:" + else + CONFLICT_BODY+=$'\n\n'"Please resolve this manually" + CONFLICT_BODY+=" (automatic resolution requires \`GH_PAT_WORKFLOWS\` to be configured):" + fi + CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" + CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" + CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" + CONFLICT_BODY+=$'\n'"git add ."$'\n'"git commit"$'\n'"git push"$'\n'"\`\`\`" + gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$CONFLICT_BODY" fi - CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin" - CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH" - CONFLICT_BODY+=$'\n'"# resolve conflicts, then:" - CONFLICT_BODY+=$'\n'"git add ."$'\n'"git commit"$'\n'"git push"$'\n'"\`\`\`" - gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$CONFLICT_BODY" else echo " Warning: failed to update PR #$PR_NUMBER — $UPDATE_OUTPUT" fi diff --git a/.github/workflows/claude-code-reusable.yml b/.github/workflows/claude-code-reusable.yml index 7fd588b80..b415738aa 100644 --- a/.github/workflows/claude-code-reusable.yml +++ b/.github/workflows/claude-code-reusable.yml @@ -355,10 +355,11 @@ jobs: if: >- github.event_name == 'issue_comment' && github.event.issue.pull_request && - contains(github.event.comment.body, '') + contains(github.event.comment.body, '