You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
push_to_pull_request_branch: bundle generated for current checkout branch, not the named pull_request_number — apply fails with "couldn't find remote ref" in multi-PR batch workflows #41643
push_to_pull_request_branch silently generates a git bundle for the wrong branch in multi-PR batch workflows, because the bundle's source branch is derived from the working-tree's current HEAD at MCP-call time, while the apply step independently derives the destination branch from the pull_request_number argument. When those two disagree, the apply step fails with:
Error: Failed to apply bundle: Failed to fetch bundle: fatal: couldn't find remote ref refs/heads/<pr-head-branch>
and the safe output never lands. The PR instead receives a warning comment:
Warning
The push_to_pull_request_branch operation failed: Failed to apply bundle. The code changes were not applied.
Where it happens
This is reproducible with batch workflows that loop over many PRs, git checkout each PR branch, and call push_to_pull_request_branch(pull_request_number=X). Observed in production runs of the github/github-automationDeep Clean workflow operating cross-repo against github/github (sparse, shallow checkout, PAT-authenticated):
The source branch (what goes into the bundle) and the destination branch (where the apply step pushes) are computed from two independent, unreconciled sources:
MCP collection time — pushToPullRequestBranchHandler in actions/setup/js/safe_outputs_handlers.cjs strips any agent-supplied branch and sets:
The accompanying comment encodes the (unenforced) assumption:
Derive it from the current checkout: the working tree must be on the PR head ref because that's what the agent committed onto. The apply-time push job independently re-derives the destination from pulls.get(pull_number)...
The bundle filename and its internal ref are then built from this entry.branch — generate_git_bundle.cjs runs git bundle create <file> <baseRef>..<branchName>, so the bundle contains refs/heads/<currentBranch>.
Apply time — actions/setup/js/push_to_pull_request_branch.cjs resolves the destination independently from the pull_request_number:
while the bundle file itself is located via message.branch (the current-checkout branch) in resolve_transport_paths.cjs.
There is no validation that getCurrentBranch() corresponds to the PR identified by pull_request_number. When the agent's working tree is checked out on a different branch than the PR it names — which happens whenever tool calls interleave with git checkout, run in parallel, or execute out of order in a batch loop — the bundle is named after, and contains a ref for, branch A, but the apply step tries to git fetch refs/heads/B (B = the named PR's head). The bundle has no refs/heads/B, so git fetch fails with couldn't find remote ref refs/heads/B.
The agent in run 28172063942 actually observed the mismatch itself:
# Switch to 437916 branch for its push (the bundle ended up on 438016 so
# 437916 still needs a push)
Concrete evidence
Run 28196257479, message 1/12 (from the safe_outputs log):
The bundle is …authorization-platform… but the target branch is …lifecycle…. The bundle only contains refs/heads/…authorization-platform…, so fetching refs/heads/…lifecycle… cannot succeed.
Run 28172063942, message 1/11 shows the same pattern: bundle …minitest-assertions-marketplace… vs target branch …style-mechanical-hosted-compute….
Why the existing recovery paths don't catch it
The apply step has recovery logic for missing prerequisite commits (shallow/fetch-depth:1 races, cf. #32310/#32467) but that path only triggers when the bundle ref exists and is missing ancestors. Here the bundle ref is entirely absent (wrong branch name), so extractBundlePrerequisiteCommits() returns empty and the handler goes straight to throw new Error("Failed to fetch bundle: ..."). This is a different failure mode than the shallow-clone bundle issues (#31600, #32310, #32467) and from the create_pull_request ref-name mismatch (#31918, #32069), all of which are closed.
Suggested fixes (any one closes the gap)
Bind source to the named PR at MCP time. When pull_request_number / target is supplied, resolve the PR head ref via the API in pushToPullRequestBranchHandler and assert the current checkout matches it. If not, fail fast with a clear, actionable error (or check out the correct ref before generating the bundle) instead of silently bundling the wrong branch.
Make the apply step fetch the ref the bundle actually contains. Record the bundle's real source branch in the safe-output message and, at apply time, fetch that ref from the bundle, then update-ref the API-resolved destination head to the bundle tip. This decouples "ref inside bundle" from "destination branch name".
At minimum, add a guard + diagnostic in the apply handler: if message.branch (the bundle's source) differs from the resolved pullRequest.head.ref, emit an explicit error like "bundle was generated for branch A but PR #N head is branch B; the working tree was not on the PR head ref when push_to_pull_request_branch was called" rather than the opaque couldn't find remote ref.
Environment
gh-aw safe-outputs runtime (MCP collection + apply jobs), cross-repo push with target-repo + per-handler github-token (PAT), sparse + shallow (fetch-depth: 1) checkout of the target repo.
Summary
push_to_pull_request_branchsilently generates a git bundle for the wrong branch in multi-PR batch workflows, because the bundle's source branch is derived from the working-tree's currentHEADat MCP-call time, while the apply step independently derives the destination branch from thepull_request_numberargument. When those two disagree, the apply step fails with:and the safe output never lands. The PR instead receives a warning comment:
Warning
The
push_to_pull_request_branchoperation failed: Failed to apply bundle. The code changes were not applied.Where it happens
This is reproducible with batch workflows that loop over many PRs,
git checkouteach PR branch, and callpush_to_pull_request_branch(pull_request_number=X). Observed in production runs of thegithub/github-automationDeep Clean workflow operating cross-repo againstgithub/github(sparse, shallow checkout, PAT-authenticated):safe_outputs83446470284)safe_outputs83529619197)Root cause
The source branch (what goes into the bundle) and the destination branch (where the apply step pushes) are computed from two independent, unreconciled sources:
MCP collection time —
pushToPullRequestBranchHandlerinactions/setup/js/safe_outputs_handlers.cjsstrips any agent-suppliedbranchand sets:The accompanying comment encodes the (unenforced) assumption:
The bundle filename and its internal ref are then built from this
entry.branch—generate_git_bundle.cjsrunsgit bundle create <file> <baseRef>..<branchName>, so the bundle containsrefs/heads/<currentBranch>.Apply time —
actions/setup/js/push_to_pull_request_branch.cjsresolves the destination independently from thepull_request_number:while the bundle file itself is located via
message.branch(the current-checkout branch) inresolve_transport_paths.cjs.There is no validation that
getCurrentBranch()corresponds to the PR identified bypull_request_number. When the agent's working tree is checked out on a different branch than the PR it names — which happens whenever tool calls interleave withgit checkout, run in parallel, or execute out of order in a batch loop — the bundle is named after, and contains a ref for, branch A, but the apply step tries togit fetch refs/heads/B(B = the named PR's head). The bundle has norefs/heads/B, sogit fetchfails withcouldn't find remote ref refs/heads/B.The agent in run
28172063942actually observed the mismatch itself:Concrete evidence
Run
28196257479, message 1/12 (from thesafe_outputslog):The bundle is
…authorization-platform…but the target branch is…lifecycle…. The bundle only containsrefs/heads/…authorization-platform…, so fetchingrefs/heads/…lifecycle…cannot succeed.Run
28172063942, message 1/11 shows the same pattern: bundle…minitest-assertions-marketplace…vs target branch…style-mechanical-hosted-compute….Why the existing recovery paths don't catch it
The apply step has recovery logic for missing prerequisite commits (shallow/fetch-depth:1 races, cf. #32310/#32467) but that path only triggers when the bundle ref exists and is missing ancestors. Here the bundle ref is entirely absent (wrong branch name), so
extractBundlePrerequisiteCommits()returns empty and the handler goes straight tothrow new Error("Failed to fetch bundle: ..."). This is a different failure mode than the shallow-clone bundle issues (#31600, #32310, #32467) and from the create_pull_request ref-name mismatch (#31918, #32069), all of which are closed.Suggested fixes (any one closes the gap)
Bind source to the named PR at MCP time. When
pull_request_number/targetis supplied, resolve the PR head ref via the API inpushToPullRequestBranchHandlerand assert the current checkout matches it. If not, fail fast with a clear, actionable error (or check out the correct ref before generating the bundle) instead of silently bundling the wrong branch.Make the apply step fetch the ref the bundle actually contains. Record the bundle's real source branch in the safe-output message and, at apply time, fetch that ref from the bundle, then
update-refthe API-resolved destination head to the bundle tip. This decouples "ref inside bundle" from "destination branch name".At minimum, add a guard + diagnostic in the apply handler: if
message.branch(the bundle's source) differs from the resolvedpullRequest.head.ref, emit an explicit error like "bundle was generated for branch A but PR #N head is branch B; the working tree was not on the PR head ref when push_to_pull_request_branch was called" rather than the opaquecouldn't find remote ref.Environment
target-repo+ per-handlergithub-token(PAT), sparse + shallow (fetch-depth: 1) checkout of the target repo.