Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
57 changes: 43 additions & 14 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,8 @@ jobs:
steps:
- name: Update behind non-Dependabot PRs
env:
GH_TOKEN: ${{ github.token }}
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)
Expand Down Expand Up @@ -99,21 +109,40 @@ 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.
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"
# 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="<!-- auto-rebase-conflict:"
SENTINEL="${SENTINEL_PREFIX}${BASE_SHA} -->"
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
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="<!-- auto-rebase-conflict -->"
CONFLICT_BODY="$SENTINEL"
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."
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:"
Expand Down
125 changes: 125 additions & 0 deletions .github/workflows/claude-code-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,128 @@ 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:
# 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 &&
contains(github.event.comment.body, '<!-- auto-rebase-conflict:') &&
contains(fromJson('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)
concurrency:
group: claude-rebase-${{ github.event.issue.number }}
cancel-in-progress: false
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 — GITHUB_TOKEN pushes skip CI. Set 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, you MUST determine
which side is newer before choosing a side:
- For **tag pins** (e.g. `v3.1.2`), compare the tag names as semver — prefer the higher version.
- For **SHA pins**, resolve each SHA to a commit date via the API:
```
gh api repos/{owner}/{repo}/git/ref/tags/{tag} --jq '.object.sha' # tag → SHA
gh api repos/{owner}/{repo}/branches/{branch} --jq '.commit.sha' # branch → SHA
gh api repos/{owner}/{repo}/git/commits/{sha} --jq '.committer.date' # SHA → date
```
Accept the side with the more recent commit date.
- When the base branch has a newer or equal version, prefer the base branch (`git checkout --ours -- <file>`).
- When the PR branch is provably newer, keep it (`git checkout --theirs -- <file>`).
- If the version cannot be determined, treat it as ambiguous and abort (step 7b).
- 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:**
- Do not attempt to merge application-code conflicts.
- Run `git rebase --abort` immediately and go to step 7b.
- Application logic must be resolved by a human.

### 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
12 changes: 9 additions & 3 deletions standards/ci-standards.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<!-- 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.

**No secrets required** — uses `GITHUB_TOKEN` only. 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 `<!-- 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.

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

Expand Down