Skip to content

Feat: Add Merge Group support#292

Merged
tykeal merged 11 commits into
dcoapp:mainfrom
bramwelt:feat/merge-queue-support
Jul 22, 2026
Merged

Feat: Add Merge Group support#292
tykeal merged 11 commits into
dcoapp:mainfrom
bramwelt:feat/merge-queue-support

Conversation

@bramwelt

@bramwelt bramwelt commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds a `merge_group.checks_requested` event handler so the DCO check runs when a PR is added to a merge queue
  • Parses the PR number from the merge group's `head_ref` — supports both real GitHub format (`refs/heads/gh-readonly-queue//pr--`) and the bare format (`gh-readonly-queue//pr--`); strips any `refs/heads/` prefix before using it as `head_branch` in the check run
  • Validates all commits in the merge group by comparing `merge_group.base_sha...merge_group.head_sha`, covering batched PRs — not just the single PR identified in `head_ref`
  • Reports the check result on the merge group's `head_sha` (the temporary merge commit); the generated merge commit is automatically skipped by DCO validation as it has two parents
  • Adds error handling on `pulls.get`: 404/403 return early gracefully; all other errors (5xx, timeouts, rate limits) are rethrown so they surface and can be retried/alerted
  • Adds `merge_group` to `default_events` in `app.yml`
  • Registers `test/fixtures/merge_group.checks_requested.json` in `REUSE.toml` so `reuse lint` passes

Post-merge action required

After merging, the Merge group event subscription must be manually enabled in the GitHub App settings:

GitHub → Settings → Developer settings → GitHub Apps → DCO → Permissions & events → Subscribe to events → check "Merge group"

Note: `app.yml` changes do not automatically update existing GitHub App settings — they only apply to new installations created from the manifest.

Test plan

  • All existing tests pass (`npm test`)
  • New `merge_group event` tests pass (8 tests: failing check, passing check, 403 fallback to status API, PR lookup 404 skips, PR lookup 403 skips, non-404/403 error rethrows, bare `head_ref` format, and unrecognized `head_ref` format)
  • 100% code coverage maintained
  • Enable merge queue on a test repo branch, open a PR, add to merge queue, verify DCO check appears on the merge group SHA

🤖 Generated with Claude Code

@bramwelt
bramwelt requested review from a team and Copilot July 14, 2026 00:41
@bramwelt bramwelt changed the title Feat: Add merge_group.checks_requested support Feat: Add Merge Group support Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Adds support for GitHub’s merge queue “merge group” checks flow so the DCO app reports its result against the merge-group SHA, ensuring DCO validation runs when a PR enters the merge queue.

Changes:

  • Add a merge_group.checks_requested handler that parses the PR number from merge_group.head_ref, fetches the PR, and reports the DCO check on merge_group.head_sha.
  • Extend the core check() function to optionally report to an override SHA/ref (to support merge groups).
  • Update the GitHub App manifest events and bump the smee-client dev dependency.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
index.js Adds merge group handler and allows check() to report against a specified SHA/ref.
test/index.test.js Adds merge-group tests for passing/failing/ignored head_ref formats.
test/fixtures/merge_group.checks_requested.json Adds a merge_group webhook fixture used by tests.
app.yml Adds merge_group to default_events in the app manifest.
package.json Updates smee-client dev dependency version.
package-lock.json Locks updated dependency graph for smee-client@5.0.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread index.js
Comment thread test/index.test.js
Comment thread test/index.test.js
Comment thread package.json Outdated
Copilot AI review requested due to automatic review settings July 14, 2026 00:49
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

@bramwelt is attempting to deploy a commit to the DCO App Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.

Comment thread test/fixtures/merge_group.checks_requested.json
Comment thread test/index.test.js Outdated
Comment thread test/index.test.js Outdated
Comment thread package.json Outdated
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 22, 2026 12:40pm

@tykeal tykeal left a comment

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.

Thanks for tackling merge-queue support (#199) — this is a genuinely useful addition, and the implementation is in good shape overall: the check() refactor to accept a report SHA/ref is clean, commits are DCO signed-off, CI is green, and the new tests cover the single-PR and unrecognized-head_ref cases well.

I'm requesting changes on two points below.

🔴 Blocking: merge groups can batch multiple PRs, but only one is validated

The handler parses a single PR number from head_ref:

const match = mergeGroup.head_ref.match(/\/pr-(\d+)-/);

then fetches that one PR and validates its base..head commits, reporting the result on the merge group's head_sha.

A GitHub merge group can batch multiple PRs into a single merge group. In that case:

  • the regex (no g flag) captures only the first pr-<N> segment,
  • only that one PR's commits are DCO-validated,
  • but the check is reported as pass/fail for the entire merge group head_sha.

That means unsigned commits belonging to other PRs batched into the same merge group could pass the DCO check. For a check whose whole purpose is enforcing sign-off, that's a correctness gap.

The merge_group payload already gives you everything needed to validate the merge group's actual contents directly — it includes both base_sha and head_sha:

"base_sha": "607c64cd...",
"head_sha": "abc123def456..."

Consider validating the merge group's own commit range instead of fetching a single PR — e.g. compareCommits({ base: merge_group.base_sha, head: merge_group.head_sha }) and running the existing DCO logic over those commits. That would:

  • cover all commits actually in the merge group (multi-PR safe),
  • remove the fragile head_ref regex parsing, and
  • drop the extra pulls.get API call.

Please also add a test covering a merge group that batches more than one PR, so this behavior is locked in.

❓ Scope question: the smee-client v5 bump

This PR also upgrades smee-client (^1.0.15.0.0). Was that required to get the merge-queue work functioning locally, or is it an incidental dev-loop fix you hit along the way? It reads as unrelated to merge-queue support (it's a local-webhook-proxy dev dependency, tracked separately in #264), and bundling it means the two changes can't be reviewed/merged independently.

  • If it isn't required for this feature, I'd prefer it be split into its own PR against #264 so each can land on its own merits.
  • If you'd like to keep it here, could you pin it as ^5.0.0 rather than the exact 5.0.0? #264's acceptance criterion is ^5, and the caret lets patch releases flow. (Note #264 also asks that local webhook delivery be verified — the checkbox in your test plan is still unchecked.)

Happy to help once these are addressed — thanks again for the contribution!

@tykeal

tykeal commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Heads up: now that #293 (the smee-client v5 upgrade) has merged to main, this PR has fallen into a conflicting state — GitHub currently shows it as not mergeable, because it still carries its own smee-client changes to package.json / package-lock.json that now collide with what landed in #293.

Could you rebase this branch onto the latest main and drop the smee-client commit/changes from it? After the rebase, this PR should be scoped purely to the merge-queue work:

  • app.yml (add merge_group)
  • index.js (the merge_group.checks_requested handler)
  • test/index.test.js + test/fixtures/merge_group.checks_requested.json
  • REUSE.toml

with no package.json / package-lock.json changes at all.

That will clear the conflict and let the review focus on the merge-queue implementation (including the multi-PR merge-group point from my earlier review). Thanks!

bramwelt and others added 2 commits July 16, 2026 12:36
Run the DCO status check when commits are added to a
merge queue. Fetches the original PR using the PR number
parsed from the merge group's head_ref, runs DCO
validation against the PR's commits, and reports the
result on the merge group's head_sha.

The merge_group.head_ref fixture uses the real GitHub
payload format (no refs/heads/ prefix).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Register test/fixtures/merge_group.checks_requested.json
in REUSE.toml so reuse lint passes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 16, 2026 19:36
@bramwelt
bramwelt force-pushed the feat/merge-queue-support branch from 8e7281c to 2ec8adf Compare July 16, 2026 19:36
@bramwelt

Copy link
Copy Markdown
Contributor Author

Rebased onto main — the branch now contains only 2 commits (the merge_group feature and the REUSE.toml fix), with no package.json/package-lock.json changes. The smee-client work is isolated to #293. Also fixed the head_ref fixture and test assertions per the Copilot review comments.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread index.js
Comment thread app.yml
Anchor the regex to the gh-readonly-queue/ prefix so
only real merge queue refs are matched, preventing
accidental matches on unrelated refs containing /pr-N-.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 16, 2026 22:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Comment thread index.js
Use merge_group.base_sha...merge_group.head_sha for the
commit comparison so all commits in a batched merge group
are DCO-validated, not just the single PR parsed from
head_ref.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 16, 2026 23:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

index.js:169

  • merge_group.head_ref is assumed to start with gh-readonly-queue/.... Some merge_group payloads/sources include the full ref (refs/heads/gh-readonly-queue/...). With the current anchored regex, those events would be ignored (no check reported) and head_branch could be sent as a full ref if it ever includes the prefix. Consider normalizing an optional refs/heads/ prefix before matching and before passing it into check().
    const mergeGroup = context.payload.merge_group;
    const match = mergeGroup.head_ref.match(
      /^gh-readonly-queue\/.+\/pr-(\d+)-/
    );
    if (!match) return;
    const prNumber = parseInt(match[1], 10);

    const { data: pr } = await context.octokit.rest.pulls.get(
      context.repo({ pull_number: prNumber })
    );

    await check(context, pr, {
      reportSha: mergeGroup.head_sha,
      reportRef: mergeGroup.head_ref,
      baseSha: mergeGroup.base_sha,
      headSha: mergeGroup.head_sha,
    });

Comment thread index.js
Add try/catch around the pulls.get call so that a 404
or other error gracefully returns instead of throwing
an unhandled exception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 20, 2026 17:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread test/index.test.js
Update the merge_group head_ref regex from
/^gh-readonly-queue\/.../ to /(?:^|\/)gh-readonly-queue\/.../
so payloads with a refs/heads/ prefix are matched and not
silently dropped.

Add test covering the refs/heads/ prefixed head_ref case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 20, 2026 22:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

index.js:156

  • The PR number extraction regex is not anchored, so it will match any ref that merely contains a /gh-readonly-queue/.../pr-<N>- segment (e.g. some-prefix/gh-readonly-queue/...), which can trigger unintended PR lookups/check creation. Since the intent is to accept only the merge-queue ref format (optionally prefixed with refs/heads/), tighten the pattern accordingly.
    const match = mergeGroup.head_ref.match(
      /(?:^|\/)gh-readonly-queue\/.+\/pr-(\d+)-/
    );

- Strip refs/heads/ prefix from reportRef before using
  it as head_branch in check runs, consistent with how
  pr.head.ref is always a bare branch name
- Add comments at merge_group call site explaining why
  reportSha and headSha share the same value

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 21, 2026 00:10

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

index.js:156

  • merge_group.head_ref parsing regex is broader than the documented/expected formats. /(?:^|\/)gh-readonly-queue\/.../ will also match refs that merely contain /gh-readonly-queue/ mid-string (e.g. foo/gh-readonly-queue/...), which could lead to extracting an unintended PR number and calling pulls.get for the wrong PR. Since the PR description and tests indicate only gh-readonly-queue/... and optional refs/heads/ prefix are valid, tighten the regex to only allow that optional prefix at the start.
    const match = mergeGroup.head_ref.match(
      /(?:^|\/)gh-readonly-queue\/.+\/pr-(\d+)-/
    );

Real GitHub merge_group.checks_requested payloads always
include refs/heads/ prefix in head_ref (confirmed from
live webhook captures). Update the fixture and repurpose
the prefix test to instead cover the bare head_ref format.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 21, 2026 16:48

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread index.js
Previously all errors from pulls.get() were caught and
returned early, silently dropping the DCO check on
transient 5xx/timeout failures. Now only 404 and 403
are handled gracefully; other errors are rethrown.

Add tests for the 403 early-return and 422 rethrow paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Trevor Bramwell <tbramwell@linuxfoundation.org>
Copilot AI review requested due to automatic review settings July 21, 2026 16:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@tykeal tykeal left a comment

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.

Looks good to me 👍

@tykeal
tykeal merged commit f9dc684 into dcoapp:main Jul 22, 2026
7 of 8 checks passed
ModeSevenIndustrialSolutions pushed a commit that referenced this pull request Jul 22, 2026
Document the GitHub App permissions and event subscriptions that
operators must keep enabled, including the GitHub UI display names for
webhook events.

Also describe how to apply changes to an already-running app, including
manual settings sync and the re-approval nuance for permission changes.
This addresses enablement confusion from #289, #290, and #292.

Clarify permission scopes, adding-permission re-approval, and the
organization-owned app settings path.

Signed-off-by: Andrew Grimberg <agrimberg@linuxfoundation.org>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bramwelt
bramwelt deleted the feat/merge-queue-support branch July 22, 2026 17:37
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