Skip to content

PR #22012 fix for create-pull-request signed commits on new branches is incorrect — createCommitOnBranch does not auto-create branches #22564

@bbonafed

Description

@bbonafed

Summary

Issue #21990 reported that pushSignedCommits fails for create-pull-request when the target branch is new (does not exist on remote). PR #22012 was merged to fix this, but the fix is incorrect: it assumes the GraphQL createCommitOnBranch mutation will auto-create a branch when given a parent OID. It does not — the mutation returns "Branch not found" and the code falls back to unsigned git push, which is rejected by GH013 (Require signed commits ruleset).

This is the same blocking scenario described in #21990, just with a different intermediate error.

Environment

What PR #22012 Changed vs. What Actually Happens

PR #22012 added this logic to push_signed_commits.cjs:

if (!expectedHeadOid) {
  // Branch does not exist on the remote yet – createCommitOnBranch will create it.
  // Use the local parent commit OID as the expected base.
  core.info(`pushSignedCommits: branch ${branch} not yet on the remote, resolving parent OID for first commit`);
  const { stdout: parentOut } = await exec.getExecOutput("git", ["rev-parse", `${sha}^`], { cwd });
  expectedHeadOid = parentOut.trim();
}

The comment "createCommitOnBranch will create it" is incorrect. The createCommitOnBranch mutation requires the branch to already exist on the remote. When the branch does not exist, the mutation returns:

Request failed due to following response errors:
 - Branch not found

Error trace (from production run, repo/branch names redacted)

pushSignedCommits: replaying 1 commit(s) via GraphQL createCommitOnBranch (branch: <new-branch-name>, repo: <owner>/<repo>)
pushSignedCommits: processing commit 1/1 sha=<commit-sha>
/usr/bin/git ls-remote origin refs/heads/<new-branch-name>
pushSignedCommits: branch <new-branch-name> not yet on the remote, resolving parent OID for first commit
/usr/bin/git rev-parse <commit-sha>^
<parent-sha>
pushSignedCommits: using parent OID for new branch: <parent-sha>
pushSignedCommits: calling createCommitOnBranch mutation (expectedHeadOid=<parent-sha>)
Warning: pushSignedCommits: GraphQL signed push failed, falling back to git push: Request failed due to following response errors:
 - Branch not found
/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.

Comparison with #21990

#21990 (before PR #22012) This issue (after PR #22012)
ls-remote returns empty Threw "Could not resolve remote HEAD OID" Resolves parent OID correctly
createCommitOnBranch called? No Yes, but fails with "Branch not found"
Fallback to git push Yes Yes
GH013 rejection Yes Yes — same outcome

Proposed Fix

The fix proposed in #21990 remains correct: create the branch on the remote before calling createCommitOnBranch.

In push_signed_commits.cjs, when git ls-remote returns empty (branch doesn't exist on remote), use the GraphQL createRef mutation to bootstrap the branch from the base branch HEAD before replaying commits:

// In the block where expectedHeadOid is empty (branch not on remote):

// Step 1: Get the repository node ID
const { repository } = await githubClient.graphql(`
  query($owner: String!, $repo: String!) {
    repository(owner: $owner, name: $repo) { id }
  }
`, { owner, repo });

// Step 2: Create the branch on the remote pointing at the parent commit
const { stdout: parentOut } = await exec.getExecOutput("git", ["rev-parse", `${sha}^`], { cwd });
const parentOid = parentOut.trim();

const { createRef } = await githubClient.graphql(`
  mutation($repositoryId: ID!, $name: String!, $oid: GitObjectID!) {
    createRef(input: {
      repositoryId: $repositoryId
      name: $name
      oid: $oid
    }) {
      ref { target { oid } }
    }
  }
`, {
  repositoryId: repository.id,
  name: `refs/heads/${branch}`,
  oid: parentOid,
});

// Step 3: Use the created ref's OID as expectedHeadOid
expectedHeadOid = createRef.ref.target.oid;

// Now createCommitOnBranch will succeed because the branch exists

This replaces the current incorrect assumption that createCommitOnBranch auto-creates branches.

Where to Apply

actions/setup/js/push_signed_commits.cjs

Replace lines inside the if (!expectedHeadOid) block (the "Branch does not exist" case):

CURRENT (broken):
  ls-remote → empty → rev-parse parent → pass parent OID to createCommitOnBranch → "Branch not found" → fallback git push → GH013

FIXED:
  ls-remote → empty → rev-parse parent → createRef (bootstrap branch) → get OID → createCommitOnBranch succeeds

Related

Impact

Same as #21990: blocks all create-pull-request safe outputs in repositories with "Require signed commits" rulesets. push-to-pull-request-branch continues to work (branch already exists on remote).

Metadata

Metadata

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions