Feat: Add Merge Group support#292
Conversation
There was a problem hiding this comment.
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_requestedhandler that parses the PR number frommerge_group.head_ref, fetches the PR, and reports the DCO check onmerge_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-clientdev 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.
|
@bramwelt is attempting to deploy a commit to the DCO App Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tykeal
left a comment
There was a problem hiding this comment.
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
gflag) captures only the firstpr-<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_refregex parsing, and - drop the extra
pulls.getAPI 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.1 → 5.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.0rather than the exact5.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!
|
Heads up: now that #293 (the Could you rebase this branch onto the latest
with no 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! |
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>
8e7281c to
2ec8adf
Compare
|
Rebased onto main — the branch now contains only 2 commits (the merge_group feature and the REUSE.toml fix), with no |
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>
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>
There was a problem hiding this comment.
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_refis assumed to start withgh-readonly-queue/.... Somemerge_grouppayloads/sources include the full ref (refs/heads/gh-readonly-queue/...). With the current anchored regex, those events would be ignored (no check reported) andhead_branchcould be sent as a full ref if it ever includes the prefix. Consider normalizing an optionalrefs/heads/prefix before matching and before passing it intocheck().
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,
});
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>
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>
There was a problem hiding this comment.
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 withrefs/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>
There was a problem hiding this comment.
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_refparsing 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 callingpulls.getfor the wrong PR. Since the PR description and tests indicate onlygh-readonly-queue/...and optionalrefs/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>
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>
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>
Summary
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"
Test plan
🤖 Generated with Claude Code