Azure DevOps: "TF401028: The reference 'refs/heads/…' has already been updated by another client, so you cannot update it. Please try again."
Azure Repos moves a branch only if the branch still points at the commit you say it does. This time it did not: something moved the branch after you read it, or the branch you meant to create already exists. Nothing was changed. Read the branch's current commit and try again on top of it.
The whole answer
From the REST API, on POST …/_apis/git/repositories/{repository}/pushes, as a public paklo issue reported it:
409 Conflict
{"$id":"1","innerException":null,"message":"TF401028: The reference 'refs/heads/dependabot/nuget/…' has already been updated by another client, so you cannot update it. Please try again.","typeName":"Microsoft.TeamFoundation.Git.Server.GitReferenceStaleException, Microsoft.TeamFoundation.Git.Server","typeKey":"GitReferenceStaleException","errorCode":0,"eventId":3000}
From git push, in a public report where several releases updated one wiki at the same time:
! [remote rejected] wikiMaster -> wikiMaster (TF401028: The reference 'refs/heads/wikiMaster' has already been updated by another client, so you cannot update it. Please try again.) error: failed to push some refs to 'https://…visualstudio.com/…/_git/….wiki'
In code, check typeKey for GitReferenceStaleException rather than the text.
Why the branch was not moved
Each branch update in a push carries an oldObjectId: the commit the branch points at now. For a branch that does not exist yet, Microsoft's Pushes - Create samples send forty zeros. If the ID no longer matches when Azure Repos makes the change, it refuses. The Refs - Update Refs page calls the same case staleOldObjectId and says "the caller lost a race to update the ref" is the most likely reason. In practice:
- Another push came first: a colleague, a pipeline, or a second run of the same tool, like the releases updating one wiki above.
- The tool read the branch earlier and kept that commit ID, for example between creating a branch and adding a file to it.
- Forty zeros, but the branch is already there, often from an earlier run. In a public autoupdate pull request, GitHub answered that case with 422 "Reference already exists", GitLab with 400 "Branch already exists", and Azure DevOps with 409 and TF401028.
Fix it in a tool that calls the REST API
Read the branch's commit right before the push:
GET https://dev.azure.com/{organization}/{project}/_apis/git/repositories/{repository}/refs?filter=heads/main&api-version=7.1
The commit is in value[].objectId. Two things we saw on a public repository: filter matches the start of the name, so heads/mas returned refs/heads/master; and filter=refs/heads/master returned nothing. Leave out refs/, and take the entry whose name is exactly refs/heads/<branch>.
Send that ID as oldObjectId. If your change depends on what is in the branch, such as an edit to a file, build it again from that commit. Then push. If TF401028 comes back, read and push again; sending the same old ID a second time can only fail the same way.
For "create the branch if it is missing", treat TF401028 on a push with forty zeros as a sign that the branch exists, read it as above, and decide whether to reuse it. The autoupdate pull request fixed its case by reading that refusal as "already exists".
Fix it for git push
git pull --rebase
git push
This puts your commits on top of the branch as it is now. Two jobs that push the same branch at the same time will keep running into each other, so let them run one after the other.
The same number on work items
TF401028 also comes back with a different text when a work item gets a link it does not accept. In microsoft/azure-devops-mcp issue 1412, a link to a wiki page sent with attributes.name "Wiki" was refused with TF401028 Unrecognized Resource link; Azure DevOps expects "Wiki Page". That one has nothing to do with branches.
Create Azure DevOps pull requests from IntelliJ IDEA and Rider
Azure DevOps Pull Requests, a plugin by Lesstep for JetBrains IDEs 2024.1 or later and Android Studio, adds Git → Create Azure DevOps Pull Request…. It opens a pull request from the branch you are on, into the repository's default branch unless you pick another, optionally as a draft. If the branch is not on Azure DevOps yet, it says so and opens the IDE's push dialog first. Creating pull requests is Pro (US$1 a month, with a 30-day free trial); the review inbox is free.
Get it on JetBrains Marketplace
Azure DevOps and Azure Repos are trademarks of Microsoft Corporation; IntelliJ IDEA and Rider are trademarks of JetBrains s.r.o. This page is not affiliated with or endorsed by Microsoft or JetBrains.
More: "TF401192" when completing (the same kind of refusal for a pull request) · "TF401179: An active pull request for the source and target branch already exists" · 日本語