-
Notifications
You must be signed in to change notification settings - Fork 312
Description
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
- gh-aw runtime: post-v0.62.5 (PR fix: pushSignedCommits fails on new branches with "Require signed commits" ruleset #22012 code confirmed executing via log messages)
- github.com (not GHES)
- Repository ruleset: "Require signed commits" enabled on all branches
- Safe output:
create-pull-request(new branch)
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 existsThis 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
create-pull-requestsigned commits fail when branch does not yet exist on remote #21990 — Original issue (closed by PR fix: pushSignedCommits fails on new branches with "Require signed commits" ruleset #22012, but fix is incorrect)- fix: pushSignedCommits fails on new branches with "Require signed commits" ruleset #22012 — PR with the incomplete fix
- fix: replace git push with GraphQL signed commits to satisfy required_signatures rulesets #21576 — Original PR adding
pushSignedCommits - refactor: extract pushSignedCommits to shared helper, add integration tests #21584 — Refactor into shared
push_signed_commits.cjs
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).