Skip to content

ci(backport): add mergiraf + Claude fallback and workflow_dispatch trigger#4375

Open
sveitser wants to merge 13 commits into
mainfrom
ma/agent-backport-action
Open

ci(backport): add mergiraf + Claude fallback and workflow_dispatch trigger#4375
sveitser wants to merge 13 commits into
mainfrom
ma/agent-backport-action

Conversation

@sveitser
Copy link
Copy Markdown
Collaborator

  • install latest mergiraf and nightly rustfmt; korthout uses draft_commit_conflicts so conflicts produce draft PRs with markers committed
  • single Claude step finalizes each created PR: mergiraf solve, manual fixups, just fmt, commit, push, gh pr ready
  • new workflow_dispatch with pr_number and target_branch inputs for manually-triggered backports when the label was forgotten
  • new scripts/backport-dispatch cherry-picks for the dispatch path, handling true merge commits via -m 1

…igger

- install latest mergiraf and nightly rustfmt; korthout uses draft_commit_conflicts so conflicts produce draft PRs with markers committed
- single Claude step finalizes each created PR: mergiraf solve, manual fixups, just fmt, commit, push, gh pr ready
- new workflow_dispatch with pr_number and target_branch inputs for manually-triggered backports when the label was forgotten
- new scripts/backport-dispatch cherry-picks for the dispatch path, handling true merge commits via -m 1
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented May 22, 2026

Claude finished @sveitser's task in 1m 43s —— View job


Code Review: ci(backport): add mergiraf + Claude fallback and workflow_dispatch trigger

  • Read repo guidelines and existing review comments
  • Review .github/workflows/backport.yml changes
  • Review scripts/backport-dispatch (new file)
  • Post detailed review feedback

Summary

Well-structured PR that adds two useful capabilities to the backport workflow: automated conflict resolution via mergiraf + Claude, and manual workflow_dispatch for forgotten labels. The scripts/backport-dispatch script has solid error handling (merged-check, SHA validation, conflict-vs-other-failure distinction).

Actionable items (posted as inline comments):

  1. id-token: write permission — doesn't appear to be needed by any step; removing it tightens the security scope.
  2. Missing timeout-minutes on the Claude step — without a cap, the agent could run indefinitely on edge cases and burn CI minutes.
  3. Author attribution — the dispatch path captures login but doesn't use it for PR assignment; --assignee "${login}" on gh pr create would match korthout's behavior.

Non-blocking observations:

  • The parent_count > 2 merge-commit detection logic is correct but non-obvious — a short inline comment would help future readers.
  • Conflict handling correctly mirrors korthout's draft_commit_conflicts: non-conflicted files are already staged by cherry-pick, then conflicted marker files are added on top.
  • The prompt concatenation of both step outputs is fine in practice (one will always be empty), just yields a harmless extra space.

Overall this looks good — nice layered approach with mergiraf as first pass and Claude as fallback.

pull-requests: write # so it can create pull requests
contents: write
pull-requests: write
id-token: write
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is id-token: write needed here? The original workflow only had contents: write and pull-requests: write. Neither the korthout action, the dispatch script, nor the Claude action appear to require OIDC tokens. If this was added for a future need, it'd be worth commenting why — otherwise removing it would tighten the permission scope.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread .github/workflows/backport.yml Outdated
Comment on lines +58 to +72
- name: Finalize backport PRs
if: |
steps.backport.outputs.created_pull_numbers != '' ||
steps.dispatch_backport.outputs.created_pull_numbers != ''
uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
prompt: |
Finalize these draft backport PRs:
${{ steps.backport.outputs.created_pull_numbers }} ${{ steps.dispatch_backport.outputs.created_pull_numbers }}

For each PR: check it out, resolve any conflict markers (try `mergiraf solve <file>` first), run `just fmt`, commit and push, then `gh pr ready <pr>`.

Keep mergiraf-resolved commits separate from your manual resolutions for a clear audit trail.
claude_args: "--model claude-opus-4-6"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two suggestions for the Claude finalization step:

  1. Add a timeout-minutes — if the agent hits edge cases (many conflicted files, a hang), this step could run for a very long time and burn CI minutes. Something like timeout-minutes: 30 seems reasonable.

  2. Consider what happens when both outputs are empty but the if condition is truthy — if korthout/backport-action succeeds but creates no PRs (e.g. label exists but target branch doesn't), created_pull_numbers would be '', so the if correctly skips. Looks fine.

  3. Minor: the prompt concatenates both outputs with a space (${{ steps.backport.outputs.created_pull_numbers }} ${{ steps.dispatch_backport.outputs.created_pull_numbers }}). Since only one path runs per invocation, one will always be empty, yielding a leading/trailing space. Claude will handle this fine, but you could use a conditional expression or trim if you want it cleaner.

Comment thread scripts/backport-dispatch Outdated
Comment on lines +43 to +48
parent_count=$(git rev-list --parents -n 1 "${sha}" | wc -w)
if (( parent_count > 2 )); then
cherry_args=(-x -m 1)
else
cherry_args=(-x)
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: git rev-list --parents -n 1 outputs <sha> <parent1> [<parent2>...], so wc -w returns 1 + num_parents. The condition parent_count > 2 means "has more than 1 parent" (i.e., is a merge commit). This is correct, but the indirection through word-counting is non-obvious — a short comment would help a future reader.

Also, for a regular (non-merge) commit with 1 parent, wc -w = 2, so -m 1 is correctly skipped. Good.

Comment thread scripts/backport-dispatch
Comment on lines +53 to +61
if [[ "${cherry_status}" -ne 0 ]]; then
mapfile -t conflicted < <(git diff --name-only --diff-filter=U)

if [[ ${#conflicted[@]} -eq 0 ]]; then
echo "cherry-pick failed (status ${cherry_status}) with no conflicted paths" >&2
exit 1
fi

git add -- "${conflicted[@]}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When cherry-pick fails due to conflicts, non-conflicted changes are already staged but conflicted files have markers in the working tree. The git add -- "${conflicted[@]}" stages those marker files, then git commit captures everything. This is correct and mirrors what korthout's draft_commit_conflicts does.

One edge case to consider: if the cherry-pick fails for a reason other than conflicts (e.g., the commit doesn't exist, or an empty commit), git diff --name-only --diff-filter=U returns nothing, and the script correctly exits with an error on line 57. Good error handling.

Comment thread scripts/backport-dispatch
Comment on lines +76 to +81
pr_url=$(gh pr create \
--base "${target_branch}" \
--head "${branch}" \
--draft \
--title "[Backport ${target_branch}] ${title}" \
--body "Backport of #${pr_number} to \`${target_branch}\`.")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding --assignee "${login}" to attribute the backport PR to the original author. The login is already captured on line 29 but only used in the log message. The korthout action typically does this attribution automatically for its path.

sveitser added 3 commits May 22, 2026 12:45
Repo squash-merges all PRs, so the merge commit always has a single
parent; -m 1 detection is dead code.
…t message

- single gh pr view call instead of two
- drop unused login field and defensive sha null check
- inline the two-line commit body instead of heredoc
sveitser added 6 commits May 22, 2026 15:01
gh pr view --json doesn't expose a 'merged' field; use 'state' and
check for 'MERGED' instead.
- mergiraf is invoked automatically during cherry-pick (both korthout and
  scripts/backport-dispatch) via a global gitattributes entry
- post-step just scans each created PR branch for surviving conflict
  markers and only sends those to Claude
- clean and mergiraf-resolved backports are marked ready without
  invoking Claude
- cherry-pick produces a new commit on each run (fresh committer
  timestamp), so a re-run of the same backport produces a different
  history on the same branch name; switch to --force-with-lease
- if a PR for the head branch already exists (re-run case), reuse its
  number instead of trying to create a duplicate
Replaces the prior force-push approach with an explicit early-exit check
using git ls-remote. If a previous backport attempt left a branch
behind, the user must delete it (and close any open PR) before
retrying. Avoids silently overwriting work.
- set git user.name/user.email globally in the job so every git
  operation, including any commit Claude makes, defaults to the
  github-actions bot identity
- drop the now-redundant local git config in scripts/backport-dispatch
- tell Claude in the prompt to never use --amend, --author=,
  --reuse-message=, git rebase, or cherry-pick --continue, since those
  preserve the original PR author's identity
- cherry-pick -x intentionally still preserves the original author of
  the cherry-picked commit; that is fine
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 22, 2026

Nextest failures (1) in this run

Test Attempts Time (s) Main history
hotshot-testing::tests_5::tests_5::combined_network::test_combined_network_cdn_crash 1 76.73 passing

See the step summary for flaky tests and slowest tests.

Comment thread .github/workflows/backport.yml Outdated
Mergiraf left conflict markers in these backport PRs:
${{ steps.resolve.outputs.needs_claude }}

For each PR: check it out, resolve the markers, run `just fmt`, commit, push, then `gh pr ready <pr>`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe it could also run cargo check --tests --examples? (we could even add rust-cache here)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any way to avoid it using gh? I'm sort of worried about it doing weird stuff

I guess at a minimum it would be nice if there was some (guaranteed) indicator that claude had to fix some of the merge conflicts, so we need to pay special attention reviewing backport PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the cache is quite tricky on release branches, i think it would just take a long time to run tests and they are flaky, we run CI anyway

i will try to lock down stuff with claude

it would already be a separate commit if claude was invoked on top of the others

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sveitser added 3 commits June 5, 2026 15:58
- Drop `gh` CLI from Claude's prompt; workflow now handles ready/label
- Add `claude-resolved` label to PRs Claude touched as a reviewer signal
- Run `cargo check --tests --examples` with rust-cache after Claude
  resolves conflicts; failure leaves PR as draft
- Claude commits to local branches only; no push, no `gh`
- Verify step checks out each local branch, runs `cargo check`, then
  pushes. cargo check failure leaves origin unchanged so reviewer sees
  the original failed-cherry-pick state instead of a broken resolution
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants