How gh-stack Integrates with the GitHub Merge Queue: Detection and Async Routing
gh-stack detects whether the target branch uses a merge queue via GraphQL, then routes the merge request through the /pulls/{n}/merge-async endpoint with the merge_action parameter explicitly set to "merge_queue" when a queue is configured.
The github/gh-stack CLI extension automates the management of stacked pull requests, handling the complex dependencies between branches. When your repository configures GitHub’s merge queue for branch protection, gh-stack automatically detects this setting and adapts its merge workflow to enqueue entire stacks rather than attempting direct merges, ensuring compliance with repository rules while preserving stack integrity.
Detecting Merge Queue Configuration
Before initiating any merge operation, gh-stack queries repository metadata to determine if the base branch requires merge queue processing. This detection occurs early in the merge flow to decide which UI prompts to display and which API parameters to include.
GraphQL Detection Logic
The detection implementation resides in internal/github/merge_async.go. The baseBranchUsesMergeQueue function queries the repository via GraphQL to check for the presence of a mergeQueue object or a MERGE_QUEUE branch protection rule:
usesMergeQueue := baseBranchUsesMergeQueue(client, base)
According to the source code at lines 60-67, this function returns true if either condition is present, signaling that the base branch enforces merge queue requirements. This boolean result drives subsequent logic in cmd/merge.go within the runMerge orchestration function.
Branch Rule Evaluation
The helper specifically examines the repository's branch protection settings through the GitHub GraphQL API. When the query identifies an active merge queue configuration, gh-stack skips the standard merge-method selection workflow because the queue itself will determine the final merge strategy.
Routing the Async Merge Request
Once detection completes, gh-stack adjusts both the user interface and the underlying API request to respect the queue configuration.
Selecting the Merge Action
In cmd/merge.go, the runMergeHeadless function constructs the appropriate merge action based on the detection result:
mergeAction := mergeActionFor(usesMergeQueue) // "merge_queue" when true, otherwise "default"
res, err := client.MergeStackAsync(targetPR, method, mergeAction)
The mergeActionFor helper maps the boolean to specific constants defined in internal/github/merge_async.go (lines 28-33):
MergeActionDefault– Delegates the decision to the server based on repository settingsMergeActionDirectMerge– Forces immediate merge bypassing the queueMergeActionMergeQueue– Explicitly targets the merge queue for asynchronous processing
Submitting to the Merge-Async Endpoint
The MergeStackAsync function submits a POST request to the REST endpoint /pulls/{n}/merge-async with a JSON payload containing:
- The
merge_method(when applicable) - The
merge_actionfield set to"merge_queue"when the base branch uses a queue
When the GitHub API receives merge_action: "merge_queue", it routes the entire stack into the configured merge queue rather than performing an immediate merge.
User Experience and CLI Feedback
After submitting the async request, gh-stack provides distinct terminal feedback based on the detection result. The implementation in cmd/merge.go (lines 70-74) prints:
- With merge queue:
"Adding #12, #13, #14 to the merge queue for main..." - Without merge queue:
"Merging #12, #13, #14 into main via squash..."
This differentiation ensures users understand whether their stack is entering the merge queue for asynchronous validation or completing immediately via direct merge.
Implementation Examples
Detecting Queue Configuration
func usesMergeQueue(client *github.Client, base string) bool {
// Returns true if the base branch has a merge queue or a MERGE_QUEUE rule.
ok, _ := client.BaseBranchUsesMergeQueue(base)
return ok
}
Submitting with Queue Awareness
func submitStackMerge(client *github.Client, pr int, method string, base string) error {
// Determine if the base branch uses a merge queue.
usesQueue, _ := client.BaseBranchUsesMergeQueue(base)
// Choose the appropriate merge_action.
action := github.MergeActionDefault
if usesQueue {
action = github.MergeActionMergeQueue
}
// Fire the async request.
result, err := client.MergeStackAsync(pr, method, action)
if err != nil {
return err
}
// Handle the possible outcomes.
switch {
case result.IsMerged():
fmt.Printf("Stack merged directly (SHA %v)\n", result.Details.SHA)
case result.IsEnqueued():
fmt.Println("Stack added to the merge queue")
case result.IsFailed():
fmt.Printf("Merge failed: %s\n", result.Details.Message)
}
return nil
}
Command-Line Usage
# Base branch uses a merge queue → enqueue the entire stack
$ gh stack merge --yes
Adding #12, #13, #14 to the merge queue for main...
# Base branch does NOT use a merge queue → direct merge with chosen method
$ gh stack merge --squash
Merging #12, #13, #14 into main via squash...
Summary
- GraphQL Detection: The
baseBranchUsesMergeQueuefunction ininternal/github/merge_async.goqueries formergeQueueobjects orMERGE_QUEUEbranch rules to determine if the base branch requires queue processing. - Action Constants: Three merge actions are defined in
merge_async.go:MergeActionDefault,MergeActionDirectMerge, andMergeActionMergeQueue. - API Routing: When a queue is detected, gh-stack sends
merge_action: "merge_queue"to the/pulls/{n}/merge-asyncendpoint, causing the server to enqueue rather than immediately merge the stack. - UI Adaptation: The CLI skips merge-method selection and displays "Adding to merge queue" messages when
usesMergeQueuereturnstrue.
Frequently Asked Questions
How does gh-stack detect if my repository uses a merge queue?
gh-stack calls baseBranchUsesMergeQueue in internal/github/merge_async.go, which executes a GraphQL query to check if the base branch has an active mergeQueue configuration or a MERGE_QUEUE branch protection rule. If either exists, the function returns true and gh-stack adjusts its workflow to use the asynchronous merge queue path.
What endpoint does gh-stack use to submit stacks to the merge queue?
When a merge queue is detected, gh-stack sends a POST request to the /pulls/{n}/merge-async REST endpoint with a JSON payload containing merge_action: "merge_queue". This parameter instructs the GitHub API to enqueue the stack for asynchronous processing according to the repository's queue configuration.
Why does gh-stack skip the merge method selection when a merge queue is present?
When a merge queue is configured, the queue itself determines whether to use merge, squash, or rebase based on repository settings. Therefore, gh-stack skips the interactive merge-method selection step in the wizard because the user's choice would be overridden by the queue configuration anyway.
Can I force gh-stack to bypass the merge queue and merge directly?
The source code defines a MergeActionDirectMerge constant, but the standard runMergeHeadless flow in cmd/merge.go automatically selects the action based on branch detection results. Attempting to bypass the queue may violate branch protection rules, so gh-stack defaults to respecting the repository's merge queue configuration when detected.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →