Skip to content

[WIP] Fix create-pull-request signed commits failure for new branches - #22008

Closed
dsyme with Copilot wants to merge 1 commit into
mainfrom
copilot/fix-signed-commits-issue
Closed

[WIP] Fix create-pull-request signed commits failure for new branches#22008
dsyme with Copilot wants to merge 1 commit into
mainfrom
copilot/fix-signed-commits-issue

Conversation

CopilotAI commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Thanks for asking me to work on this. I will get started on it and keep this PR's description up to date as I form a plan and make progress.


This section details on the original issue you should resolve

<issue_title>create-pull-request signed commits fail when branch does not yet exist on remote</issue_title>
<issue_description>## Summary

The pushSignedCommits feature introduced in PR github/gh-aw#21576 (shipped in v0.61.1) does not work for create-pull-request safe outputs when the target branch is new (does not yet exist on the remote). The GraphQL createCommitOnBranch mutation requires a valid expectedHeadOid obtained via git ls-remote, which returns empty for a nonexistent branch. The code then falls back to an unsigned git push, which is rejected by repositories with "Require signed commits" rulesets (GH013).

This is a blocking issue for any repository that:

  1. Uses gh-aw agentic workflows with create-pull-request safe outputs
  2. Has a "Require signed commits" ruleset enabled (org or repo level)

Environment

  • gh-aw version: v0.62.4 (latest as of 2026-03-19)
  • GitHub.com (not GHES)
  • Repository ruleset: "Require signed commits" enabled on all branches

Steps to Reproduce

  1. Configure a repository with a "Require signed commits" branch ruleset
  2. Set up an agentic workflow that uses the create-pull-request safe output
  3. Trigger the workflow so the agent produces a patch for a new branch (branch does not already exist on the remote)
  4. Observe the safe output handler attempt to push

Actual Behavior

/usr/bin/git ls-remote --heads origin <new-branch-name>
/usr/bin/git rev-list --reverse origin/main..HEAD
<commit-sha>
pushSignedCommits: replaying 1 commit(s) via GraphQL createCommitOnBranch
/usr/bin/git ls-remote origin refs/heads/<new-branch-name>
Warning: pushSignedCommits: GraphQL signed push failed, falling back to git push: Could not resolve remote HEAD OID for branch <new-branch-name>
/usr/bin/git push origin <new-branch-name>
remote: error: GH013: Repository rule violations found for refs/heads/<new-branch-name>.
remote: - Commits must have verified signatures.
remote: Found 1 violation:
remote: <commit-sha>
error: failed to push some refs to 'https://github.com/<owner>/<repo>.git'

The workflow then falls back to creating a "fallback issue" instead of a pull request.

Expected Behavior

The create-pull-request flow should successfully push signed commits to a new branch and open a pull request, even when "Require signed commits" rulesets are active.

Root Cause Analysis

The issue is in push_signed_commits.cjs. The flow for replaying commits via GraphQL is:

1. git ls-remote origin refs/heads/<branch> → get expectedHeadOid
2. For each commit: call GraphQL createCommitOnBranch mutation

Step 1 fails when the branch is new because git ls-remote returns no OID for a nonexistent remote ref.

The push-to-pull-request-branch safe output does not have this problem because its branch already exists on the remote at the time of push.

Contrasting the Two Code Paths

Safe OutputBranch Stategit ls-remote ResultSigned Push Works?
push-to-pull-request-branchAlready exists on remoteReturns valid OIDYes
create-pull-requestNew, local onlyReturns emptyNo — falls back to unsigned git push

Proposed Fix

Before replaying commits via the GraphQL createCommitOnBranch mutation, the code should create the remote branch ref when it doesn't already exist. The GitHub REST API provides this capability:

Option A: Use the Git References API to Bootstrap the Branch (Recommended)

In push_signed_commits.cjs (or create_pull_request.cjs), before the GraphQL replay loop:

// In push_signed_commits.cjs, after getting an empty OID from ls-remote:asyncfunctionensureRemoteBranchExists(octokit,owner,repo,branchName,baseSha){// Check if branch existstry{const{ data }=awaitoctokit.rest.git.getRef({
owner,
repo,ref: `heads/${branchName}`,});returndata.object.sha;// Branch exists, return its HEAD}catch(e){if(e.status!==404)throwe;}// Branch doesn't exist — create it pointing at the base branch HEADconst{ data }=awaitoctokit.rest.git.createRef({
owner,
repo,ref: `refs/heads/${branchName}`,sha: baseSha,// SHA of the base branch (e.g., main) HEAD});returndata.object.sha;// This becomes the expectedHeadOid for the first commit}

Then wire this into the signed push flow:

// Current code (simplified):letheadOid=awaitgetRemoteHeadOid(branch);// returns null for new branchif(!headOid){// CURRENT: throw/fallback to git push// FIX: create the branch on the remote firstconstbaseSha=awaitgetBaseBranchSha();// SHA of the base branch (main/master)headOid=awaitensureRemoteBranchExists(octokit,owner,repo,branch,baseSha);}// Now replay commits via GraphQL createCommitOnBranch using headOidfor(constcommitofcommits){constresult=awaitgraphql(createCommitOnBranchMutation,{branch: {repositoryNameWithOwner: `${owner}/${repo}`,branchName: branch},expectedHeadOid: headOid,// ... commit data});headOid=result.createCommitOnBranch.commit.oid;// Update for next commit}

Option B: Use GraphQL createRef Mutation

If the team prefers staying within GraphQL:

mutationCreateRef($repositoryId: ID!, $name: String!, $oid: GitObjectID!) {
createRef(input: {
repositoryId: $repositoryIdname: $name # "refs/heads/<branch-name>"oid: $oid # base branch HEAD SHA
}) {
ref {
nametarget { oid }
}
}
}

This creates the branch on the remote with a valid commit (the base branch HEAD), providing the expectedHeadOid needed for the subsequent createCommitOnBranch calls.

Where to Apply the Fix

The change should go into push_signed_commits.cjs at the point where expectedHeadOid resolution fails. Specifically:

actions/setup/js/push_signed_commits.cjs

The fix should be applied before the fallback to git push, replacing the current error-catch behavior:

BEFORE (current):
ls-remote → empty → catch → "Could not resolve remote HEAD OID" → fallback to git push
AFTER (fixed):
ls-remote → empty → createRef (bootstrap branch from base) → get OID → replay via GraphQL

Related Issues and PRs

ReferenceDescription
#21572Meta issue: "Signed commits" (closed)
#21576PR that added pushSignedCommits via GraphQL createCommitOnBranch (merged)
#21584Refactor: moved logic into shared push_signed_commits.cjs (merged)
#21562Enterprise blocker for create-pull-request + required_signatures (closed)

Impact

This blocks all create-pull-request safe outputs in repositories with signed commit requirements. Since create-pull-request is the primary mechanism for agents to submit code changes as PRs, this effectively prevents agentic workflows from operating in repositories with standard security rulesets.

Affected safe outputs:

  • create-pull-requestbroken (new branch)
  • push-to-pull-request-branch — works (existing branch)
    </issue_description>

Comments on the Issue (you are @copilot in this section)

@dsyme@bbonafed Great issue report, thanks you

📍 Connect Copilot coding agent with Jira, Azure Boards or Linear to delegate work to Copilot in one click without leaving your project management tool.

@mnkiefer

Copy link
Copy Markdown
Collaborator

CopilotAI requested a review from dsymeMarch 20, 2026 18:10
Copilot stopped work on behalf of dsyme due to an error March 20, 2026 18:10
@github-actionsgithub-actionsBot mentioned this pull request Mar 20, 2026
@github-actions
github-actionsBot deleted the copilot/fix-signed-commits-issue branch April 2, 2026 13:07
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

create-pull-request signed commits fail when branch does not yet exist on remote

3 participants

@mnkiefer@dsyme