Summary
The stack-specific asynchronous merge endpoint accepts a GitHub App installation access token, returns 202 / pending, and then fails in the background with:
Enqueuer is not authorized to merge
This happens even though:
- the installation includes the repository;
- the App has
Contents: write;
- the response advertises
X-Accepted-GitHub-Permissions: contents=write;
- the App also has
Merge queues: write and Pull requests: write; and
- the same installation token passes the authorization check for the ordinary GraphQL
enqueuePullRequest mutation.
The REST documentation says that merge-async supports GitHub App installation access tokens and requires Contents: write. The request therefore appears to pass the documented endpoint-level authorization, then fail a second, user-role-oriented authorization check in the asynchronous stack worker.
Environment
- GitHub.com native stacked pull requests, public preview
- Direct REST API calls; this does not depend on a
gh-stack client-side code path
- Five-PR stack targeting a trunk branch protected by a merge queue
- All five PRs were open, non-draft, approved, and
MERGEABLE
- All required status contexts were green
- One lower PR had an unrelated non-required failed check and therefore reported
UNSTABLE; the required contexts were green. The terminal API error was authorization-specific, not a repository-rule error.
- The top PR's ordinary
mergeStateStatus was BLOCKED by its unmerged stack dependencies; the stack-specific endpoint is expected to merge the contiguous stack from the bottom up.
- Authentication: fresh GitHub App installation access token for
hmnd-alphaclaw[bot]
- Relevant installation permissions:
contents: write
merge_queues: write
pull_requests: write
actions: write
workflows: write
- The repository is included in the installation's repository selection.
- Under the installation token, GraphQL
repository.viewerPermission is null. GitHub's App documentation says installation-token requests depend on App permissions rather than a human repository role, so this should not remove the documented endpoint access.
The affected repository is private. Repository and PR identifiers are omitted from this public report, but the request UUID and timestamps below should allow GitHub staff to correlate the server-side job. Full identifiers can be provided privately through GitHub Support.
Reproduction
Use a GitHub App installation token as GH_TOKEN. The installation must include the repository and grant at least Contents: write.
Create a native stack whose PRs satisfy the target branch's required reviews and checks, then request a merge of the top PR through the merge queue:
owner='<owner>'
repo='<repo>'
pull_number='<top-pr-number>'
head_sha='<current-top-pr-head-sha>'
submit="$(gh api -X PUT \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"repos/$owner/$repo/pulls/$pull_number/merge-async" \
-f sha="$head_sha" \
-f merge_action=merge_queue)"
printf '%s\n' "$submit" | jq .
uuid="$(printf '%s\n' "$submit" | jq -r '.details.uuid')"
# Poll the asynchronous result.
gh api \
-H 'Accept: application/vnd.github+json' \
-H 'X-GitHub-Api-Version: 2022-11-28' \
"repos/$owner/$repo/pulls/$pull_number/merge-async/$uuid" | jq .
Actual result
Submission at 2026-09-04T22:32:25Z returned:
{
"status": "pending",
"details": {
"message": "Merge request enqueued.",
"uuid": "5fccdf27-0e4c-4a11-8a7e-520f3097dfd3",
"merge_method": "default",
"merge_action": "merge_queue",
"expected_head_sha": "<current-top-pr-head-sha>"
}
}
Polling at 2026-09-04T22:36:40Z returned:
{
"status": "failed",
"details": {
"message": "Enqueuer is not authorized to merge"
}
}
No pull request was merged or added to the merge queue. All five PRs remained open.
Expected result
Because the endpoint documentation explicitly supports GitHub App installation access tokens and requires Contents: write, one of these should happen:
- the stack is added to the merge queue; or
- if installation actors are not actually supported for
merge_action=merge_queue, the initial request fails synchronously with a documented 4xx response that identifies the missing requirement.
Returning 202 with "Merge request enqueued." and only later rejecting the actor contradicts the documented token/permission contract and makes submission look successful until the UUID is polled.
Authorization control
To separate ordinary merge-queue permission from stack-specific behavior, the same fresh installation token was used for a deliberately non-mutating GraphQL probe:
- call
enqueuePullRequest on an ordinary PR;
- supply an intentionally incorrect
expectedHeadOid;
- observe
expected head oid does not match the current head oid, rather than an authorization error; and
- confirm
mergeQueueEntry remains null.
This proves the mutation reached the expected-head validation after authorization without changing queue state. Before Merge queues: write was granted, the same mutation failed at authorization. After it was granted and a fresh token was minted, authorization passed—but the native stack worker still produced the terminal error above.
Documented contract
GitHub's REST documentation for Merge a pull request asynchronously states that:
- this is the required method for stacked PRs;
- GitHub App installation access tokens are supported;
- the required fine-grained permission is
Contents: write; and
- branch protection and repository rules are not evaluated when the initial request is accepted, only later in the background job.
GitHub's GitHub App permissions documentation also says:
The success of an API request with an installation access token only depends on the app's permissions.
A human repository role is therefore not documented as an additional requirement for this token type.
Why this looks server-side
The request passes all observable front-door checks:
- supported token type;
- repository included in the installation;
- documented
Contents: write permission present;
- accepted-permissions response header matches that permission;
- current head SHA supplied; and
- initial request accepted as
pending.
The failure appears only when the asynchronous worker evaluates the enqueuer. One possible explanation is that the worker checks a human-style repository role for the App actor, sees viewerPermission: null, and rejects it instead of evaluating installation repository access plus App permissions. That is only a hypothesis; GitHub will need to confirm the actual backend path.
Requested fix / clarification
- Confirm whether GitHub App installation access tokens are supported for
merge-async when merge_action=merge_queue, as the REST documentation currently states.
- If supported, authorize the asynchronous enqueuer using installation repository access and the documented App permissions rather than requiring a human repository role.
- If unsupported, update the documentation and reject the initial request synchronously with an actionable
4xx error.
- Do not return
"Merge request enqueued." before the worker has established that the actor can enqueue it, or make the provisional nature unmistakable.
- Document what
repository.viewerPermission: null means for installation actors and ensure it is not treated as “no repository access” by stack workers.
- Add an integration test covering an installation token with
Contents: write and merge_action=merge_queue.
Related issues (no exact duplicate found)
- #268 covered ordinary enqueue/auto-merge APIs rejecting stacks; the async stack route added in
gh-stack v0.1.0 addressed that routing problem, not this worker authorization failure.
- #315 covers missing admin-bypass behavior in stack merges and reports other cases where
merge-async accepts work it cannot complete.
- #323 covers stack merges failing despite approved, mergeable PRs, but does not report GitHub App installation-token authorization.
Searches of open and closed github/gh-stack issues found no report containing GitHub App, installation token, or the exact error Enqueuer is not authorized to merge.
Summary
The stack-specific asynchronous merge endpoint accepts a GitHub App installation access token, returns
202/pending, and then fails in the background with:This happens even though:
Contents: write;X-Accepted-GitHub-Permissions: contents=write;Merge queues: writeandPull requests: write; andenqueuePullRequestmutation.The REST documentation says that
merge-asyncsupports GitHub App installation access tokens and requiresContents: write. The request therefore appears to pass the documented endpoint-level authorization, then fail a second, user-role-oriented authorization check in the asynchronous stack worker.Environment
gh-stackclient-side code pathMERGEABLEUNSTABLE; the required contexts were green. The terminal API error was authorization-specific, not a repository-rule error.mergeStateStatuswasBLOCKEDby its unmerged stack dependencies; the stack-specific endpoint is expected to merge the contiguous stack from the bottom up.hmnd-alphaclaw[bot]contents: writemerge_queues: writepull_requests: writeactions: writeworkflows: writerepository.viewerPermissionisnull. GitHub's App documentation says installation-token requests depend on App permissions rather than a human repository role, so this should not remove the documented endpoint access.The affected repository is private. Repository and PR identifiers are omitted from this public report, but the request UUID and timestamps below should allow GitHub staff to correlate the server-side job. Full identifiers can be provided privately through GitHub Support.
Reproduction
Use a GitHub App installation token as
GH_TOKEN. The installation must include the repository and grant at leastContents: write.Create a native stack whose PRs satisfy the target branch's required reviews and checks, then request a merge of the top PR through the merge queue:
Actual result
Submission at
2026-09-04T22:32:25Zreturned:{ "status": "pending", "details": { "message": "Merge request enqueued.", "uuid": "5fccdf27-0e4c-4a11-8a7e-520f3097dfd3", "merge_method": "default", "merge_action": "merge_queue", "expected_head_sha": "<current-top-pr-head-sha>" } }Polling at
2026-09-04T22:36:40Zreturned:{ "status": "failed", "details": { "message": "Enqueuer is not authorized to merge" } }No pull request was merged or added to the merge queue. All five PRs remained open.
Expected result
Because the endpoint documentation explicitly supports GitHub App installation access tokens and requires
Contents: write, one of these should happen:merge_action=merge_queue, the initial request fails synchronously with a documented4xxresponse that identifies the missing requirement.Returning
202with"Merge request enqueued."and only later rejecting the actor contradicts the documented token/permission contract and makes submission look successful until the UUID is polled.Authorization control
To separate ordinary merge-queue permission from stack-specific behavior, the same fresh installation token was used for a deliberately non-mutating GraphQL probe:
enqueuePullRequeston an ordinary PR;expectedHeadOid;expected head oid does not match the current head oid, rather than an authorization error; andmergeQueueEntryremainsnull.This proves the mutation reached the expected-head validation after authorization without changing queue state. Before
Merge queues: writewas granted, the same mutation failed at authorization. After it was granted and a fresh token was minted, authorization passed—but the native stack worker still produced the terminal error above.Documented contract
GitHub's REST documentation for Merge a pull request asynchronously states that:
Contents: write; andGitHub's GitHub App permissions documentation also says:
A human repository role is therefore not documented as an additional requirement for this token type.
Why this looks server-side
The request passes all observable front-door checks:
Contents: writepermission present;pending.The failure appears only when the asynchronous worker evaluates the enqueuer. One possible explanation is that the worker checks a human-style repository role for the App actor, sees
viewerPermission: null, and rejects it instead of evaluating installation repository access plus App permissions. That is only a hypothesis; GitHub will need to confirm the actual backend path.Requested fix / clarification
merge-asyncwhenmerge_action=merge_queue, as the REST documentation currently states.4xxerror."Merge request enqueued."before the worker has established that the actor can enqueue it, or make the provisional nature unmistakable.repository.viewerPermission: nullmeans for installation actors and ensure it is not treated as “no repository access” by stack workers.Contents: writeandmerge_action=merge_queue.Related issues (no exact duplicate found)
gh-stackv0.1.0 addressed that routing problem, not this worker authorization failure.merge-asyncaccepts work it cannot complete.Searches of open and closed
github/gh-stackissues found no report containingGitHub App,installation token, or the exact errorEnqueuer is not authorized to merge.