Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions .github/workflows/auto-rebase-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,23 @@
# Failure modes handled gracefully:
# - without `workflows` permission (403): posts an idempotent comment asking
# the author to rebase manually (sentinel: <!-- auto-rebase-blocked -->)
# - merge conflict (422): posts an idempotent comment asking the author to
# resolve conflicts (sentinel: <!-- auto-rebase-conflict -->)
# - merge conflict (422): posts an idempotent comment noting that Claude will
# attempt an agentic rebase (sentinel: <!-- auto-rebase-conflict -->), which
# triggers the claude-rebase job in claude-code-reusable.yml
Comment thread
don-petry marked this conversation as resolved.
#
# 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:
Expand All @@ -40,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)
Expand Down Expand Up @@ -99,27 +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="<!-- auto-rebase-conflict -->"
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="<!-- auto-rebase-conflict -->"
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+=$'\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="<!-- auto-rebase-conflict -->"
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"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
else
echo " Warning: failed to update PR #$PR_NUMBER — $UPDATE_OUTPUT"
fi
Expand Down
111 changes: 111 additions & 0 deletions .github/workflows/claude-code-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,114 @@ 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, '<!-- auto-rebase-conflict -->')
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
don-petry marked this conversation as resolved.
Outdated
concurrency:
group: claude-rebase-${{ github.event.issue.number }}
cancel-in-progress: true
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: write
id-token: write
pull-requests: write
issues: write
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: 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 }}
# 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 <baseRefName>
git rebase origin/<baseRefName>
```
(Use the actual `baseRefName` from step 2. Fetching the specific ref ensures
`origin/<baseRefName>` 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 = 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 --ours -- <file>`).
- When the PR branch has a provably newer pin, keep it (`git checkout --theirs -- <file>`).
- After resolving each file: `git add <file>`, then `git rebase --continue`.
Comment thread
don-petry marked this conversation as resolved.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

**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.
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

### 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/<baseRefName>
# resolve conflicts in <file(s)>
git add <resolved-file(s)>
git rebase --continue
git push --force-with-lease
```
# yamllint enable rule:line-length
4 changes: 2 additions & 2 deletions standards/ci-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,9 +521,9 @@
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 `<!-- auto-rebase-blocked -->`) asking the author to rebase manually.
4. On merge conflict (422): posts an idempotent comment (sentinel `<!-- auto-rebase-conflict -->`) asking the author to resolve conflicts.
4. On merge conflict (422): deletes any prior sentinel and posts a fresh comment (sentinel `<!-- auto-rebase-conflict -->`), 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.

Check failure on line 524 in standards/ci-standards.md

View workflow job for this annotation

GitHub Actions / Lint

Line length

standards/ci-standards.md:524:201 MD013/line-length Line length [Expected: 200; Actual: 320] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md013.md

**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.

Check failure on line 526 in standards/ci-standards.md

View workflow job for this annotation

GitHub Actions / Lint

Line length

standards/ci-standards.md:526:201 MD013/line-length Line length [Expected: 200; Actual: 356] https://github.com/DavidAnson/markdownlint/blob/v0.40.0/doc/md013.md
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

**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`.

Expand Down
Loading