Skip to content

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

Closed
Copilot wants to merge 1 commit intomainfrom
copilot/fix-signed-commits-issue
Closed

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

Conversation

Copy link
Contributor

Copilot AI commented Mar 20, 2026

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 Output Branch State git ls-remote Result Signed Push Works?
push-to-pull-request-branch Already exists on remote Returns valid OID Yes
create-pull-request New, local only Returns empty No — 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:

async function ensureRemoteBranchExists(octokit, owner, repo, branchName, baseSha) {
  // Check if branch exists
  try {
    const { data } = await octokit.rest.git.getRef({
      owner,
      repo,
      ref: `heads/${branchName}`,
    });
    return data.object.sha; // Branch exists, return its HEAD
  } catch (e) {
    if (e.status !== 404) throw e;
  }

  // Branch doesn't exist — create it pointing at the base branch HEAD
  const { data } = await octokit.rest.git.createRef({
    owner,
    repo,
    ref: `refs/heads/${branchName}`,
    sha: baseSha, // SHA of the base branch (e.g., main) HEAD
  });

  return data.object.sha; // This becomes the expectedHeadOid for the first commit
}

Then wire this into the signed push flow:

// Current code (simplified):
let headOid = await getRemoteHeadOid(branch); // returns null for new branch
if (!headOid) {
  // CURRENT: throw/fallback to git push
  // FIX: create the branch on the remote first
  const baseSha = await getBaseBranchSha(); // SHA of the base branch (main/master)
  headOid = await ensureRemoteBranchExists(octokit, owner, repo, branch, baseSha);
}

// Now replay commits via GraphQL createCommitOnBranch using headOid
for (const commit of commits) {
  const result = await graphql(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:

mutation CreateRef($repositoryId: ID!, $name: String!, $oid: GitObjectID!) {
  createRef(input: {
    repositoryId: $repositoryId
    name: $name        # "refs/heads/<branch-name>"
    oid: $oid           # base branch HEAD SHA
  }) {
    ref {
      name
      target { 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

Reference Description
#21572 Meta issue: "Signed commits" (closed)
#21576 PR that added pushSignedCommits via GraphQL createCommitOnBranch (merged)
#21584 Refactor: moved logic into shared push_signed_commits.cjs (merged)
#21562 Enterprise 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
Contributor

@mnkiefer mnkiefer closed this Mar 20, 2026
Copilot AI requested a review from dsyme March 20, 2026 18:10
Copilot stopped work on behalf of dsyme due to an error March 20, 2026 18:10
@github-actions github-actions bot mentioned this pull request Mar 20, 2026
Sign up for free to 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