Git error messages: a field guide to the confusing parts
Git's error messages have a reputation for being cryptic. After years of daily use, it's easy to forget that many of them are genuinely ambiguous — or that they presume knowledge most users don't have. The messages that follow are some of the most common sources of confusion, along with notes on what's actually happening and how to get unstuck.
Improving these messages isn't a trivial problem. There's no clear way to know if a proposed rewrite is actually better, the work is often unfunded, and every change has to be translated into one of the 19 languages Git currently supports. That context makes the quirks below more understandable, if not less frustrating.
When git push rejects your branch
$ git push To github.com:jvns/int-exposed ! [rejected] main -> main (non-fast-forward) error: failed to push some refs to 'github.com:jvns/int-exposed' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. $ git status On branch main Your branch and 'origin/main' have diverged, and have 2 and 1 different commits each, respectively.
The same message appears whether your branch is simply behind the remote or has genuinely diverged from it. The only way to distinguish the two cases is to check git status or run git pull. It also isn't obvious which refs failed to push — the rejected branches are marked with ! [rejected] on the line immediately above, but that detail is easy to miss.
In practice, most people push a single branch at a time, so the question of which ref was rejected is usually moot. When this happens, a quick git status will show the state of the current branch, and that's typically enough to decide what to do next.
When git pull finds diverged history
$ git pull hint: You have divergent branches and need to specify how to reconcile them. hint: You can do so by running one of the following commands sometime before hint: your next pull: hint: hint: git config pull.rebase false # merge hint: git config pull.rebase true # rebase hint: git config pull.ff only # fast-forward only hint: hint: You can replace "git config" with "git config --global" to set a default hint: preference for all repositories. You can also pass --rebase, --no-rebase, hint: or --ff-only on the command line to override the configured default per hint: invocation. fatal: Need to specify how to reconcile divergent branches.
This error presents a wall of options: configure pull.rebase false, pull.rebase true, or pull.ff only — locally or globally — or run git pull --rebase or git pull --no-rebase. For someone new to Git, parsing those choices mid-error is a lot to ask.
The practical summary is shorter: use git pull --rebase or git pull --no-rebase to resolve the immediate situation, and if you want a lasting preference, set it with git config pull.rebase false or git config pull.rebase true. The pull.ff only option is largely redundant since it matches Git's default behavior anyway, though that hasn't always been the case.
When faced with this, running git status confirms the branch state, and git log origin/main or git log shows the diverging commits. Usually git pull --rebase is the right resolution. If the goal is to discard local or remote work entirely — say, after committing to the wrong branch or amending a commit on a personal branch — git push --force or git reset --hard origin/main gets the job done.
Checking out a branch that doesn't exist
With git checkout, asking for a non-existent branch yields a confusing complaint about a path:
$ git checkout asdf error: pathspec 'asdf' did not match any file(s) known to git
The confusion stems from git checkout accepting either a branch or a path as its first argument; Git can't tell which kind of argument you meant. A message like "No such branch, commit, or path" would be clearer, but the underlying ambiguity makes this genuinely hard to improve.
git switch, which only takes a branch (unless you pass -d), has a different but related problem. It reports invalid reference: asdf instead of invalid branch: asdf:
$ git switch asdf fatal: invalid reference: asdf
The reason is that git switch tries to be helpful by also accepting tags. If you run git switch v0.1, you get:
$ git switch v0.1
fatal: a branch is expected, got tag 'v0.1'`
So "invalid reference" is Git's way of saying asdf isn't a branch, a tag, or any other ref. That would be more useful if the average user knew what a "reference" was — many don't. A practical workaround: mentally replace reference with branch in the error message. When the target is actually a tag, the message will make sense.
Detached HEAD and the HEAD^ trap
$ git checkout HEAD^ Note: switching to 'HEAD^'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -cOr undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 182cd3f add "swap byte order" button
Checking out a commit rather than a branch puts you in detached HEAD state, and the warning is famously hard to digest. Lots of effort has gone into improving it, and there's no easy fix. The reliable strategy is to rely on a shell prompt that shows when you're detached, avoid making commits in that state, and run git checkout main (or any other branch) when you're done exploring.
Rebase in progress: reading git status
Not strictly an error, but still opaque:
$ git status interactive rebase in progress; onto c694cf8 Last command done (1 command done): pick 0a9964d wip No commands remaining. You are currently rebasing branch 'main' on 'c694cf8'. (fix conflicts and then run "git rebase --continue") (use "git rebase --skip" to skip this patch) (use "git rebase --abort" to check out the original branch) Unmerged paths: (use "git restore --staged..." to unstage) (use "git add ..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a")
Two things stand out. First, You are currently rebasing branch 'main' on 'c694cf8'. appears on the fifth line, not the first — so the branch being rebased isn't immediately visible. Second, in this case c694cf8 is actually origin/main; naming the remote branch would be far more informative. A shell prompt that shows rebase state is often more useful than this output.
Rebase conflicts after a file deletion
$ git rebase main CONFLICT (modify/delete): index.html deleted in 0ce151e (wip) and modified in HEAD. Version HEAD of index.html left in tree. error: could not apply 0ce151e... wip
The confusing part here is what HEAD refers to. During a merge, HEAD is your current branch; during a rebase, it's the branch you're rebasing onto. That distinction is easy to forget mid-conflict. Listing branch names explicitly, as in this hypothetical rewrite, would remove the guesswork:
CONFLICT (modify/delete): index.html deleted on `main` and modified on `mybranch`
"Them" depends on what you ran
$ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge)Unmerged paths: (use “git add/rm
…” as appropriate to mark resolution) deleted by them: the_file no changes added to commit (use “git add” and/or “git commit -a”)
Similar ambiguity afflicts the deleted by them: status line. The identity of "them" shifts with the operation:
- During a merge,
themis the branch you merged in. - During a rebase,
themis the branch you were on when you started the rebase. - During a cherry-pick,
themis the commit being picked.
If it's not obvious who "they" are, retracing your steps helps. Running git show main --stat or similar can refresh your memory of what's on your branch.
The git clean alphabet soup
$ git clean fatal: clean.requireForce defaults to true and neither -i, -n, nor -f given; refusing to clean
Reading this error requires knowing what -i, -n, and -f stand for. Many long-time users never look them up — even -i for "interactive" can remain a mystery for a decade. The short version: -f deletes untracked files without asking, which is fast but risky; -i walks you through what will be removed, which is safer once you know what it does.



