Repository navigation
feat: support the merge_group event for merge queue checks - #1894
Open
imichaelmoore wants to merge 6 commits into
Open
imichaelmoore wants to merge 6 commits into
imichaelmoore wants to merge 6 commits into
Conversation
Workflows triggered by a merge queue failed with "Unsupported event type: merge_group". The event now runs in agent mode like the other automation events. - GitHub reports these runs as github-merge-queue[bot]; the human-actor check accepts that actor for merge_group without an allowed_bots entry. - The merge group commit carries the queued pull requests' changes, so Claude's config paths are restored from the target branch, as on PRs. The choice of restore branch moves into a tested resolveRestoreBase(). - Adds docs, an example merge queue check, and a project verify skill.
The example now passes the workflow token, so a queued PR that edits the workflow is no longer skipped by the App token exchange, and Claude stays within the job's read-only permissions. It reviews a diff written before the action runs, taken against the target branch so entries ahead in the queue are covered, and it no longer allows Bash. - security.md lists the merge queue app under Bot User Control and covers merge_group alongside pull_request_target and workflow_run. - The base ref test now proves validation runs after refs/heads/ is stripped. - Drops the local verify skill, which is unrelated to this change.
The example wrote its diff with settings taken from the checkout, which is the queued pull requests' own content. - A .gitattributes entry marking a file -diff or binary reduced it to "Binary files differ", hiding its content from the review. Attributes now come from the target branch via --attr-source, and the prompt asks Claude to open any binary file that looks like it should be text. - `ignore = all` in .gitmodules hid submodule pointer changes, so the diff passes --ignore-submodules=none. - A committed merge-group.diff symlink redirected the write. The path is removed first. - Quoting the reason on one line stopped `::` workflow commands but not the legacy `##[` form, which the runner matches anywhere in a line. Commands are now stopped with a random token while the reason prints.
- security.md listed merge_group under checkout guidance that is wrong for it: with no `ref:` the checkout is the merge group, not the base branch. It now has its own section, which says what the example relies on, that the hook caveat applies to merge groups too, and how to use the subdirectory pattern for stricter separation. The original heading and its anchor are restored. - The docs no longer claim every merge_group run is attributed to the merge queue app, and say what happens for any other actor. - The allowed_bots descriptions said the default allows no bots. They now say the merge queue app is accepted on merge_group events without an entry. The longer cell re-pads the inputs table in usage.md. - The example job sets timeout-minutes so a stalled run cannot hold up the queue.
GitHub attributes a merge_group run to whoever queued the pull request, not to github-merge-queue[bot]. That app is only the actor of the queue's push to the target branch. Drop the special case for it, so a person passes the bot check as usual and an app that queues pull requests needs an allowed_bots entry. action.yml, docs/usage.md, constants.ts and actor.ts are back to what main has. Example: - --allowedTools only pre-approves tools. Use --tools to set which tools the session has, --restricted to keep the file tools in the workspace and ignore settings files, and --strict-mcp-config to skip .mcp.json. - Diff against refs/remotes/origin/<base>, so a tag called origin/<base> cannot be picked instead of the target branch. - Block text-looking files that git reports as binary instead of reading them from the working tree, where restored paths hold the target branch's copy. - Add notes on allowed_bots, large diffs, the timeout, and the workflow file being read from the merge group commit. restoreConfigFromBase names the remote-tracking ref in full as well, for the same reason as the example. Docs: rewrite the merge_group sections of security.md and custom-automations.md to match, including what the subprocess env scrub needs on the runner and what the restore does not cover. Tests: the merge_group actor cases set their inputs explicitly, the restore has cases for a shadowing tag and local branch, and the event guards have more negative cases.
…eue check
Restore:
- Fetch the base branch with both sides of the refspec spelled out, so a
successful fetch always leaves refs/remotes/origin/<base> at the branch's
tip, whatever the configured refspec is and even if the remote has a tag
of the same name. --no-tags keeps the fetch from pulling tags in.
- Check out refs/remotes/origin/<base>^{commit}. git checkout tries its
argument as a branch name first, and that form cannot be one.
Merge queue example:
- Turn includeGitInstructions off. Claude Code otherwise opens the
conversation with git status output and recent commit subjects, which
queued pull requests write, ahead of the prompt.
- End the diff's revisions with --, treat Git LFS pointers like binary
files, and keep secret values out of the printed reason.
- Say that any failure of the review step fails the check, and when to make
the check required.
Docs: leave --add-dir out of the merge group layout, mention that a queued
skill can carry hooks, and say what makes a CODEOWNERS entry binding.
Tests: the restore cases now start with no local base branch, no
remote-tracking branch and a refspec that leaves it out, and cover a branch
named after the full ref and a remote tag named after the base branch. The
actor tests pin that github-merge-queue[bot] gets no special case.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Before: a workflow triggered by a merge queue (
on: merge_group) fails immediately with "Unsupported event type: merge_group".After:
merge_groupruns in agent mode with aprompt, likeworkflow_dispatchandschedule, so the action can serve as a merge queue check.How
merge_groupjoins the automation events. It needs aprompt, andtrack_progressis rejected as it is for the others..claude/,.mcp.json,CLAUDE.mdand the other sensitive paths are restored from the target branch before the CLI starts, the same protection pull requests get. Choosing the restore branch moved fromrun.tsinto a testedresolveRestoreBase(). Pull request behavior is unchanged.refs/remotes/origin/<base>), so a tag or local branch calledorigin/<base>cannot be picked instead. This applies to pull request events too.merge_grouprun to whoever queued the pull request, andgithub-merge-queue[bot]is only the actor of the queue's later push to the target branch. GitHub's owngithub/docsrepository relies on this in.github/workflows/restrict-merge-queue.yml. So a person passes the bot check as usual, and an app that queues pull requests needs anallowed_botsentry.examples/merge-queue-check.ymlwrites the group's diff to a file, gives Claude only Read, Grep and Glob (--restricted --strict-mcp-config --tools), passes the read-only workflow token, and fails the job unless Claude returnsblock_merge: false.docs/custom-automations.mdand a "Using this action withmerge_group" section indocs/security.md.Test plan
bun run typecheck,bun run format:checkandbun test(987 pass) on this branch merged with main. actionlint passes on the example.Ran
src/entrypoints/run.tswith a simulatedmerge_grouppayload, a temp repo with a local origin, the pinned Claude Code CLI and a mock API:.claude-pr/..mcp.jsonserver starts.allowed_botsand passes with it.origin/main, both the example's diff and the restore still use the target branch.track_progressand an invalidbase_refare rejected.Not verified: a live merge queue run, or the GitHub App token exchange for this event (the example passes
github_token, which bypasses it).