Dev & EngARTICLE

GitHub makes the async merge API GA and changes CI/CD retry logic in Python, Node, and Go

GitHub's async merge API, released as generally available on October 1, 2026, replaces the immediate merge response with a request ID that must be checked via polling, requiring adjustments to CI/CD scripts that still rely on the old synchronous behavior.

What the async merge API does differently

On October 1, 2026, GitHub announced in its official changelog that the async merge API moved out of preview and became generally available (GA). It is used to merge individual pull requests, stacked PRs, or add a PR to a merge queue, with the option to bypass protection rules when the caller has permission to do so.

GitHub changelog page announcing general availability of the async merge API, explaining the PUT and GET flow
GitHub changelog page announcing general availability of the async merge API, explaining the PUT and GET flow. Reprodução: github.blog.

The flow changes in a concrete way. Instead of a synchronous call that returns the merge result in the same response, the pattern now is to submit the request with PUT and receive a request ID back; the actual merge status only appears later, in a separate GET call using that ID. GitHub describes the reasoning like this:

The API processes merges asynchronously, helping your automations handle busy repositories without waiting for complex merges to finish in a single request.

GitHub Changelog

The changelog also makes clear that this is now the recommended way to merge PRs programmatically, replacing the synchronous REST endpoint and the GraphQL mergePullRequest mutation that most integrations use today. According to the announcement itself, it is the only merge API that supports stacked PRs.

From instant response to request ID: what breaks for teams already automating merges

The point the changelog doesn't spell out, and that matters for whoever maintains the pipeline, is this: swapping the endpoint without changing the script's logic doesn't fail with an obvious error, it fails silently. A lot of CI/CD automation in production today assumes the merge response comes back ready in the same call: the script reads a field like merged in the JSON body and moves the pipeline forward from there.

This applies both to homegrown scripts in Python using requests and to integrations in Node.js via Octokit and in Go via go-github. Many of these scripts already have their own retry layer built on top of the synchronous endpoint: they catch a 405 when the base branch changed, wait a few seconds and try again, or manually poll the PR state via a pull_request webhook with a closed event.

With the async API, this homegrown retry loses its purpose and can even conflict with the new flow. The PUT response is no longer the merge result, it's just confirmation that the request was accepted. Anyone who doesn't adapt the parsing will simply never find the field they expected, and the pipeline moves forward assuming the merge didn't happen (or did happen, depending on how the try/except was written).

What polling looks like in practice

The exact call and field names are in the documentation linked from the changelog itself, but the pattern every automation needs to implement is the same across all three languages: submit, store the ID, poll in a loop until it leaves the pending state.

In Python, the skeleton looks like this:

python
import requests, time

resp = requests.put(merge_endpoint, headers=headers, json=payload)
request_id = resp.json()["id"]

while True:
    status = requests.get(f"{merges_endpoint}/{request_id}", headers=headers).json()
    if status["state"] in ("success", "failed"):
        break
    time.sleep(2)

In Node.js, with Octokit, the difference is the same: the merge call no longer resolves with the merged PR, it resolves with an ID that feeds a setInterval or a polling function with exponential backoff. In Go, anyone using go-github needs to replace the check on the return of PullRequests.Merge() with a loop that repeatedly queries the status endpoint, usually inside a context.WithTimeout so it doesn't run forever.

In short: the business logic (deciding squash, rebase, or merge commit) doesn't change. What changes is the orchestration layer around the call, which now needs a polling loop with a timeout, instead of a direct response read.

Stacked PRs and merge queue: when async really pays off

The changelog is explicit on one point that justifies migrating for teams using certain workflows: the async API is the only one that merges stacked PRs. Teams working with stacks of interdependent PRs (a common pattern in trunk-based development flows with tools like Graphite) didn't have, until now, an official way to merge the entire chain in a single call through the REST or GraphQL API.

The same goes for teams that make heavy use of merge queues in repositories with high traffic of simultaneous PRs. In these cases, waiting for a synchronous call to finish means holding the connection open while GitHub resolves conflicts, reruns checks, and processes the queue, which is exactly the scenario the async API was designed to avoid blocking.

When it's not worth switching

Not every automation needs to migrate now. For whoever maintains a small repository, without a merge queue and without stacked PRs, and only automates a simple merge (for example, auto-merging a Dependabot PR once checks pass), the synchronous REST endpoint and the GraphQL mutation still work and are simpler to maintain: one call, one response, no polling loop to write and test.

The table summarizes the trade-off:

ScenarioWorth migrating to async
Stacked PRsYes, it's the only API that supports it
High-traffic merge queueYes, avoids a blocked connection
Simple merge, low-traffic repoNot necessarily, sync still works
Legacy script reading merged from the responseNeeds rewriting before switching

For whoever decides to migrate, the practical path is to audit every script that reads the merge response body expecting the final result, replace that read with a GET loop with backoff and a retry cap, and only then point the write call to the new async endpoint. Skipping that audit is the most common way to turn an update recommended by GitHub into a pipeline that stalls without warning.

Translated from the Brazilian Portuguese original · Read the original