Skip to content

Introduce merge PR workflow#518

Open
darkwheel wants to merge 1 commit intodevfrom
merge-dependabot-prs
Open

Introduce merge PR workflow#518
darkwheel wants to merge 1 commit intodevfrom
merge-dependabot-prs

Conversation

@darkwheel
Copy link
Copy Markdown
Contributor

This pull request adds a new GitHub Actions workflow to automate the process of combining multiple pull requests that match certain criteria into a single PR. The workflow can be run manually and provides options to filter which PRs are combined, check their status, and handle merge conflicts.

Key additions in this workflow:

Automated PR Combination Workflow:

  • Introduces a new workflow file .github/workflows/merge-prs.yml that allows maintainers to combine multiple PRs based on branch prefix, status checks, and labels, with customizable input parameters.
  • The workflow filters PRs by a specified branch prefix, ensures (optionally) that their checks are passing, and excludes PRs with a given label.
  • For each qualifying PR, the workflow attempts to merge its branch into a new or existing combined branch, tracking successes and failures (e.g., due to merge conflicts).
  • After merging, the workflow automatically creates a new PR summarizing which PRs were combined and which were skipped due to conflicts.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 3, 2026

Bundle Size Report 📦

Bundle Size
Main Process 944.72 KB
Renderer JS 467.11 KB
Renderer CSS 86.25 KB
Total 1.46 MB

Bundle Analysis Reports

The detailed bundle analysis reports are available in the workflow artifacts:

  • 📊 Main Process: stats-main.html
  • 📊 Renderer Process: stats-renderer.html

Download the artifacts from the workflow run to view interactive visualizations.


Bundle size tracking is now active! This helps prevent bundle bloat.

Comment on lines +29 to +151
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/github-script@v6
id: create-combined-pr
name: Create Combined PR
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
owner: context.repo.owner,
repo: context.repo.repo
});
let branchesAndPRStrings = [];
let baseBranch = null;
let baseBranchSHA = null;
for (const pull of pulls) {
const branch = pull['head']['ref'];
console.log('Pull for branch: ' + branch);
if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) {
console.log('Branch matched prefix: ' + branch);
let statusOK = true;
if(${{ github.event.inputs.mustBeGreen }}) {
console.log('Checking green status: ' + branch);
const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number:$pull_number) {
commits(last: 1) {
nodes {
commit {
statusCheckRollup {
state
}
}
}
}
}
}
}`
const vars = {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pull['number']
};
const result = await github.graphql(stateQuery, vars);
const [{ commit }] = result.repository.pullRequest.commits.nodes;
const state = commit.statusCheckRollup.state
console.log('Validating status: ' + state);
if(state != 'SUCCESS') {
console.log('Discarding ' + branch + ' with status ' + state);
statusOK = false;
}
}
console.log('Checking labels: ' + branch);
const labels = pull['labels'];
for(const label of labels) {
const labelName = label['name'];
console.log('Checking label: ' + labelName);
if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
console.log('Discarding ' + branch + ' with label ' + labelName);
statusOK = false;
}
}
if (statusOK) {
console.log('Adding branch to array: ' + branch);
const prString = '#' + pull['number'] + ' ' + pull['title'];
branchesAndPRStrings.push({ branch, prString });
baseBranch = pull['base']['ref'];
baseBranchSHA = pull['base']['sha'];
}
}
}
if (branchesAndPRStrings.length == 0) {
core.setFailed('No PRs/branches matched criteria');
return;
}
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
sha: baseBranchSHA
});
} catch (error) {
console.log(error);
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
return;
}

let combinedPRs = [];
let mergeFailedPRs = [];
for(const { branch, prString } of branchesAndPRStrings) {
try {
await github.rest.repos.merge({
owner: context.repo.owner,
repo: context.repo.repo,
base: '${{ github.event.inputs.combineBranchName }}',
head: branch,
});
console.log('Merged branch ' + branch);
combinedPRs.push(prString);
} catch (error) {
console.log('Failed to merge branch ' + branch);
mergeFailedPRs.push(prString);
}
}

console.log('Creating combined PR');
const combinedPRsString = combinedPRs.join('\n');
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
if(mergeFailedPRs.length > 0) {
const mergeFailedPRsString = mergeFailedPRs.join('\n');
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
}
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Combined PR',
head: '${{ github.event.inputs.combineBranchName }}',
base: baseBranch,
body: body
}); No newline at end of file

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}

Copilot Autofix

AI 3 days ago

In general, the fix is to add an explicit permissions block to the workflow (either at the root or under the specific job) that grants only the scopes required for the actions performed. This documents the workflow’s needs and prevents it from silently inheriting overly broad defaults.

For this specific workflow in .github/workflows/merge-prs.yml, the script reads and writes pull requests and branches. The write operations include creating a branch (git.createRef), merging branches (repos.merge), and creating a pull request (pulls.create). These operations require at least:

  • contents: write (for creating refs/branches and merging)
  • pull-requests: write (for creating pull requests)

To fix the issue without changing existing behavior, add a permissions section at the job level for combine-prs, right under runs-on: ubuntu-latest. This keeps the permissions scoped only to that job, rather than globally for all jobs in the workflow. The block should look like:

    permissions:
      contents: write
      pull-requests: write

No additional imports, methods, or other structural changes are necessary, because this is entirely a YAML configuration change.

Suggested changeset 1
.github/workflows/merge-prs.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/merge-prs.yml b/.github/workflows/merge-prs.yml
--- a/.github/workflows/merge-prs.yml
+++ b/.github/workflows/merge-prs.yml
@@ -27,6 +27,9 @@
   combine-prs:
     # The type of runner that the job will run on
     runs-on: ubuntu-latest
+    permissions:
+      contents: write
+      pull-requests: write
 
     # Steps represent a sequence of tasks that will be executed as part of the job
     steps:
EOF
@@ -27,6 +27,9 @@
combine-prs:
# The type of runner that the job will run on
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a manually triggered GitHub Actions workflow to consolidate multiple qualifying pull requests into a single “combined” PR, intended to reduce maintainer overhead (e.g., batching Dependabot PRs).

Changes:

  • Introduces .github/workflows/merge-prs.yml with workflow_dispatch inputs to filter PRs by branch prefix, required “green” status, and an ignore label.
  • Automatically creates a combined branch, attempts to merge each qualifying PR branch into it, then opens a new PR summarizing included/skipped PRs.

Comment on lines +25 to +33
jobs:
# This workflow contains a single job called "combine-prs"
combine-prs:
# The type of runner that the job will run on
runs-on: ubuntu-latest

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/github-script@v6
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This workflow creates branches and opens PRs but doesn’t declare any permissions:. With the default restricted GITHUB_TOKEN permissions (or org-level hardening), git.createRef, repos.merge, and pulls.create can fail. Add explicit least-privilege permissions (e.g., contents: write and pull-requests: write) at the workflow or job level so the action is reliable across repo settings.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +99
if (statusOK) {
console.log('Adding branch to array: ' + branch);
const prString = '#' + pull['number'] + ' ' + pull['title'];
branchesAndPRStrings.push({ branch, prString });
baseBranch = pull['base']['ref'];
baseBranchSHA = pull['base']['sha'];
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

baseBranch / baseBranchSHA are set from whichever matching PR is processed last. If matching PRs target different base branches, the combined branch/PR base becomes nondeterministic and merges can be incorrect. Validate that all selected PRs share the same pull.base.ref (and fail/skip otherwise) before proceeding.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +112
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
sha: baseBranchSHA
});
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

baseBranchSHA is taken from pull.base.sha (the base SHA recorded on the PR), which may be stale compared to the current tip of the base branch. Creating the combined branch from an old SHA can increase conflicts and produce an out-of-date combined PR. Prefer resolving the current base branch head SHA via git.getRef (or equivalent) once the base branch is determined.

Copilot uses AI. Check for mistakes.
Comment on lines +106 to +117
try {
await github.rest.git.createRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
sha: baseBranchSHA
});
} catch (error) {
console.log(error);
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
return;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The PR description says the workflow can merge into a new or existing combined branch, but the implementation fails the run if the branch already exists. Consider supporting an existing branch (e.g., reset it to the chosen base SHA, or allow reusing it) instead of hard-failing on createRef errors.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +80
const [{ commit }] = result.repository.pullRequest.commits.nodes;
const state = commit.statusCheckRollup.state
console.log('Validating status: ' + state);
if(state != 'SUCCESS') {
console.log('Discarding ' + branch + ' with status ' + state);
statusOK = false;
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

commit.statusCheckRollup can be null (e.g., no checks configured, checks disabled, or insufficient permissions), and commits.nodes can be empty. Accessing .state without guarding will throw and fail the workflow. Add a defensive check and treat missing/unknown rollup state as non-green (or skip with a clear message).

Suggested change
const [{ commit }] = result.repository.pullRequest.commits.nodes;
const state = commit.statusCheckRollup.state
console.log('Validating status: ' + state);
if(state != 'SUCCESS') {
console.log('Discarding ' + branch + ' with status ' + state);
statusOK = false;
const commitNodes = result.repository.pullRequest.commits.nodes || [];
const latestCommitNode = commitNodes.length > 0 ? commitNodes[0] : null;
const commit = latestCommitNode ? latestCommitNode.commit : null;
const state = commit && commit.statusCheckRollup ? commit.statusCheckRollup.state : null;
if (!state) {
console.log('Discarding ' + branch + ' because no status check rollup state is available');
statusOK = false;
} else {
console.log('Validating status: ' + state);
if(state != 'SUCCESS') {
console.log('Discarding ' + branch + ' with status ' + state);
statusOK = false;
}

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +128
await github.rest.repos.merge({
owner: context.repo.owner,
repo: context.repo.repo,
base: '${{ github.event.inputs.combineBranchName }}',
head: branch,
});
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

repos.merge uses head: branch where branch is pull.head.ref. This fails for PRs from forks because the API expects head as owner:ref in that case. Use pull.head.label or construct ${pull.head.repo.owner.login}:${pull.head.ref} when pull.head.repo.full_name !== context.repo.owner + '/' + context.repo.repo.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +144
let combinedPRs = [];
let mergeFailedPRs = [];
for(const { branch, prString } of branchesAndPRStrings) {
try {
await github.rest.repos.merge({
owner: context.repo.owner,
repo: context.repo.repo,
base: '${{ github.event.inputs.combineBranchName }}',
head: branch,
});
console.log('Merged branch ' + branch);
combinedPRs.push(prString);
} catch (error) {
console.log('Failed to merge branch ' + branch);
mergeFailedPRs.push(prString);
}
}

console.log('Creating combined PR');
const combinedPRsString = combinedPRs.join('\n');
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
if(mergeFailedPRs.length > 0) {
const mergeFailedPRsString = mergeFailedPRs.join('\n');
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
}
await github.rest.pulls.create({
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

The workflow always creates a combined PR even if every merge failed (so combinedPRs is empty). That produces a noisy PR with no combined content. Consider failing early (or skipping PR creation) when combinedPRs.length === 0, and include the merge error messages in the output/PR body to aid debugging.

Copilot uses AI. Check for mistakes.
Comment on lines +144 to +151
await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: 'Combined PR',
head: '${{ github.event.inputs.combineBranchName }}',
base: baseBranch,
body: body
}); No newline at end of file
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

Creating the PR unconditionally with a fixed title/head/base will fail if an open PR already exists for the same headbase (GitHub typically allows only one open PR per head/base). Consider checking for an existing PR for combineBranchName and updating it (or closing/replacing) instead of always calling pulls.create.

Copilot uses AI. Check for mistakes.

# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- uses: actions/github-script@v6
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

This repo’s other workflows use actions/github-script@v7 (e.g. .github/workflows/build.yml:99). Consider bumping from @v6 to @v7 to stay consistent and pick up fixes/security updates.

Suggested change
- uses: actions/github-script@v6
- uses: actions/github-script@v7

Copilot uses AI. Check for mistakes.
@Power-Maverick Power-Maverick changed the base branch from main to dev April 6, 2026 02:22
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.

3 participants