Sitelet https://github.com/github/github-mcp-server/issues/3755
Skip to content

push_files leaves an orphan branch at the default branch's head when the push fails #3755

Open

Description

@rafe-walker

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

  1. Make sure the branch new-branch does not exist.
  2. Call it with one valid file and one path that CreateTree rejects:
    {"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"}]}}
  3. Result: isError with failed to create tree: tree.path contains a malformed path component.
  4. GET /repos/OWNER/REPO/git/ref/heads/new-branch returns 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

  1. rafe-walker commented on Nov 12, 2026

    @rafe-walker
    Author

    I read #3778 against the reproduction above. This is from reading the change, not a test run.

    With the change, push_files no longer creates a missing branch up front. It builds the tree and the commit on the default branch's head and creates refs/heads/<branch> only after CreateCommit succeeds. In the repro (p4/ok.txt plus .git/config), CreateTree now fails before any ref exists, so GET git/ref/heads/new-branch should return 404. A failure in CreateCommit is handled the same way, and the PR adds tests for both.

    Two notes for review, neither blocking:

    • On the empty-repository path, initializeRepository still commits an empty README.md to 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.
    • If another writer creates the same branch between the read and the 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions