Repository navigation
Description
Describe the bug
When branch does not exist, push_files creates it from the default branch first (createReferenceFromDefaultBranch) and only then builds the tree and commit. If the tree or commit step fails, the new ref is not rolled back. The caller gets an error, but a branch pointing at the default branch's head stays behind. A retry then pushes onto that leftover branch, and it shows up in branch lists.
When the branch already exists, the same failure leaves no partial commit, so only the new-branch path is affected.
Silently creating a branch that doesn't exist is already raised as a question in #3473 (item 6). This issue is only about the ref left behind when the push then fails.
Code (main 99d6d65, unchanged since v2.1.0): pkg/github/repositories.go:1767-1772 (ref created when the branch is missing), :1795-1800 (same for the empty-repo path), then CreateTree at :1809 and CreateCommit/UpdateRef after it. Helper: pkg/github/repositories_helper.go:75. Open PR #3288 keeps the ensure-ref-first order (ensurePushFilesBranchRef), so it would not change this.
Affected version
v2.1.0 (image sha256:f6056fc1…0f8a, commit 4bcdcdf) and main nightly (image sha256:48bc16d0…338f, revision 99d6d65).
Steps to reproduce the behavior
- Make sure the branch
new-branchdoes not exist. - Call it with one valid file and one path that
CreateTreerejects:{"name":"push_files","arguments":{"owner":"OWNER","repo":"REPO","branch":"new-branch","message":"push","files":[{"path":"p4/ok.txt","content":"ok"},{"path":".git/config","content":"x"}]}} - Result:
isErrorwithfailed to create tree: tree.path contains a malformed path component. GET /repos/OWNER/REPO/git/ref/heads/new-branchreturns 200, and the branch points at the default branch's head commit.
Expected vs actual behavior
Expected: a failed push leaves the repository unchanged, with no new branch.
Actual: the error is returned and an orphan branch exists at the default branch's head.
Suggested fix: tree and commit objects don't need a ref. Create the tree and the commit (parent = default branch head) first, then create the new ref directly at the new commit. Alternatively, delete the ref on any later failure.
Logs
push_files branch=new-branch -> ISERROR failed to create tree: tree.path contains a malformed path component
GET git/ref/heads/new-branch -> 200, object.sha = faf1b251… (= default branch head) [v2.1.0 and main]
Found during an independent audit of v2.1.0 on a throwaway org and private test repo. Prepared with AI assistance (Claude Code); reproduced by two independent test runs before filing.
Activity
rafe-walker commentedon Nov 12, 2026
I read #3778 against the reproduction above. This is from reading the change, not a test run.
With the change,
push_filesno longer creates a missing branch up front. It builds the tree and the commit on the default branch's head and createsrefs/heads/<branch>only afterCreateCommitsucceeds. In the repro (p4/ok.txtplus.git/config),CreateTreenow fails before any ref exists, soGET git/ref/heads/new-branchshould return 404. A failure inCreateCommitis handled the same way, and the PR adds tests for both.Two notes for review, neither blocking:
initializeRepositorystill commits an emptyREADME.mdto the default branch before the tree step. A push that fails there still leaves that commit. That is existing behaviour, not something this PR changes.CreateRef, the call now fails with "reference already exists" instead of writing. That seems like the right outcome.Happy to re-run the original reproduction once a maintainer is happy with the approach.