Skip to content
Closed
28 changes: 17 additions & 11 deletions .github/workflows/auto-rebase-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@
# 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 with sentinel
# <!-- auto-rebase-conflict -->, which triggers the claude-rebase job in
# claude-code-reusable.yml to attempt an agentic rebase with conflict
# resolution. If Claude's rebase also fails, it posts a summary and a
# human must intervene.
#
# No secrets required — uses github.token only. No auto-merge logic.
name: Auto-rebase non-Dependabot PRs (Reusable)
Expand Down Expand Up @@ -99,25 +102,28 @@ 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.
# gh api surfaces the GitHub API error JSON: {"message":"merge conflict",...}
# Merge conflict — the merge strategy can't resolve it automatically.
# Post an idempotent sentinel comment that triggers the claude-rebase
# job in claude-code-reusable.yml to attempt an agentic rebase.
#
# Idempotent: skip if sentinel comment already exists.
# Idempotent: skip if sentinel comment already exists (prevents
# re-triggering Claude on every subsequent auto-rebase run).
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"
Comment thread
don-petry marked this conversation as resolved.
Resolved
else
echo " Posting conflict resolution request"
echo " Posting conflict comment (triggers claude-rebase job)"
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'"**Auto-rebase failed — merge conflict** — this branch conflicts"
CONFLICT_BODY+=" with \`$BASE_BRANCH\` and cannot be updated via the merge strategy."
CONFLICT_BODY+=$'\n\n'"Claude will attempt an agentic rebase with conflict resolution."
Comment thread
don-petry marked this conversation as resolved.
Resolved
CONFLICT_BODY+=" If that also fails, manual intervention is needed:"
CONFLICT_BODY+=$'\n'"\`\`\`"$'\n'"git fetch origin"
CONFLICT_BODY+=$'\n'"git merge origin/$BASE_BRANCH"
CONFLICT_BODY+=$'\n'"git rebase origin/$BASE_BRANCH"
CONFLICT_BODY+=$'\n'"# resolve conflicts, then:"
CONFLICT_BODY+=$'\n'"git add ."$'\n'"git commit"$'\n'"git push"$'\n'"\`\`\`"
CONFLICT_BODY+=$'\n'"git add ."$'\n'"git rebase --continue"$'\n'"git push --force-with-lease"$'\n'"\`\`\`"
gh pr comment "$PR_NUMBER" --repo "$REPO" --body "$CONFLICT_BODY"
Comment thread
don-petry marked this conversation as resolved.
Resolved
fi
else
Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/claude-code-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,85 @@ 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 — triggered when auto-rebase-reusable.yml posts
# a conflict sentinel comment (<!-- auto-rebase-conflict -->) on a PR.
# Checks out the branch, rebases onto the base branch, resolves conflicts using
# best-effort judgment, and pushes. Posts a summary comment on completion.
claude-rebase:
if: >-
github.event_name == 'issue_comment' &&
github.event.issue.pull_request != null &&
github.event.comment.user.login == 'github-actions[bot]' &&
contains(github.event.comment.body, '<!-- auto-rebase-conflict -->')
concurrency:
group: claude-rebase-${{ github.event.issue.number }}
cancel-in-progress: true
runs-on: ubuntu-latest
timeout-minutes: 30
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: 0
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(git*:*),Bash(gh pr checkout:*),Bash(gh pr view:*),Bash(gh pr comment:*),Bash(gh api:*),Edit,Write"
# yamllint enable rule:line-length
# yamllint disable rule:line-length
prompt: |
PR #${{ github.event.issue.number }} has a merge conflict that the auto-rebase workflow could not resolve using the fast-forward merge strategy. You've been assigned to resolve it via a rebase.

## Your task

### 1. Check out the PR branch
```
gh pr checkout ${{ github.event.issue.number }}
```

### 2. Rebase onto the base branch
```
git fetch origin
BASE=$(gh pr view ${{ github.event.issue.number }} --json baseRefName -q .baseRefName)
git rebase origin/$BASE
```

### 3. Resolve conflicts intelligently
For each conflicted file, use judgment:
- **Workflow YAML / action pins**: if main already pins the same action to a newer SHA, accept main's version (`git checkout --ours <file>` — during a rebase, `--ours` is main's side). If the PR's pin is the newer or correct one, keep it (`git checkout --theirs <file>`).
- **`.gitignore`, config files**: merge both sides — preserve all unique entries from both.
- **Application code**: carefully merge both sides. If the conflict is too complex to resolve safely, abort (see below).
- After resolving each file: `git add <file>`, then `git rebase --continue`.
Comment thread
don-petry marked this conversation as resolved.
Resolved
Comment thread
don-petry marked this conversation as resolved.
Resolved

### 4. Push on success
```
git push --force-with-lease
```

### 5. Post a summary comment on PR #${{ github.event.issue.number }}
Summarize:
- Which files had conflicts and how each was resolved
- The rebase outcome (success or failure)
- If any commits were dropped because they were fully superseded by main (0 unique commits remaining), note that the PR may be a candidate for closure

## If rebase cannot be completed safely
If conflicts involve complex application code that requires human judgment:
1. `git rebase --abort`
2. Post a comment on PR #${{ github.event.issue.number }} explaining:
- Which specific files conflict
- Why the conflict needs human resolution
- The exact conflict markers so the author can find them quickly
# yamllint enable rule:line-length