Skip to content

AI Git Troubleshooting

Lesson 8 of 8Intermediate → Advanced13 min readGitHub Copilot & AI Engineering · AI + GitVerified: git 2.43.0 on Ubuntu 24.04, September 2026

Git’s error messages are precise, complete, and written for somebody who already understands the model. That combination makes them ideal input for an assistant and makes the situation dangerous: the person asking is, by definition, the person who does not know what is happening.

This is the article in the cluster where a wrong answer can destroy work. It is built around one rule, and the rest is detail.

The difference between a useful answer and a dangerous guess is entirely in the input. Every time:

  1. The command you ran. Exactly, including flags.
  2. The complete output. Not “it failed” — the actual text, including the hints.
  3. git status. The current state.
  4. Your branch state. git log --oneline -5 and git branch -vv.
  5. What you were trying to achieve. The intended end state, not the command you thought would get there.

The fifth is the one people omit and the one that most changes the answer. “I ran git reset and my changes are gone” has several different remedies depending on whether you wanted to undo a commit, unstage a file, or discard local edits — and the output alone does not say which.

Terminal window
git branch -vv

What it doesPrints the branch, its upstream tracking relationship, and how far ahead or behind it is.

Why we run itMost push and pull confusion is a tracking or divergence problem, and this is the one command that shows it.

Expected resultA line per branch: name, short hash, upstream in brackets with ahead/behind counts, and the last commit subject.

Git’s error messages usually contain the answer. A rejected push, in full:

To /path/to/upstream.git
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '/path/to/upstream.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. If you want to integrate the remote changes,
hint: use 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Four lines of hint: explaining the cause and the remedy. Pasting only ! [rejected] throws away most of the information — and a model given only that fragment will produce a generic answer covering several causes, of which yours is one.

Paste the whole thing. It costs nothing and it is the difference between a diagnosis and a list of possibilities.

The most common confusing state and among the least dangerous.

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.
$ git status
HEAD detached at d25ae62
nothing to commit, working tree clean

What it means. HEAD points at a commit rather than a branch. Nothing is broken; commits you make here belong to no branch and become unreachable when you leave.

Safe resolution. If you made no commits, git switch - returns you. If you did, create a branch first: git switch -c recovery-branch. Both are non-destructive.

Where AI helps. Explaining what happened and which of the two you are in.

Where to be careful. A suggestion to git checkout main while you have uncommitted work in a detached state — check git status first.

What it means. Read the hints. non-fast-forward means the remote has commits you do not.

Safe resolution. git pull --rebase or git pull, then push. Which depends on your team’s history policy — see rebase vs merge.

The dangerous suggestion. git push --force. It resolves the error by overwriting whatever is on the remote, including a colleague’s work.

If you genuinely need to force — after an intentional rebase of your own branch — use the safer form:

Terminal window
git push --force-with-lease

This refuses if the remote has moved since you last fetched, which is exactly the case where --force destroys somebody’s work.

Almost always false, and the reflog is why.

Terminal window
git reflog

What it doesShows every position HEAD has occupied in this repository, most recent first.

Why we run itA commit is unreachable, not deleted. The reflog records where HEAD has been, which is how you find it again.

Expected resultOne line per movement: hash, HEAD@{n}, the action and its description.

Real output from a repository where somebody checked out a previous commit and came back:

01cad27 HEAD@{0}: checkout: moving from d25ae62c897b0f83df6715086f40e81d00264ab0 to main
d25ae62 HEAD@{1}: checkout: moving from main to HEAD~1
01cad27 HEAD@{2}: commit: two
d25ae62 HEAD@{3}: commit (initial): one

Every state HEAD has been in, with hashes. Recovery is git switch -c recovered <hash> — creating a branch, which is non-destructive and reversible.

This is the highest-value AI interaction in the whole article. Reflog output is dense and unfamiliar; asking “which of these is the state before I ran the reset?” is a reading question with a checkable answer, and the recovery command it leads to creates rather than destroys.

Note the limit: the reflog is local and expires. It does not help with something that was never in your repository.

What it means. Git stopped partway and is waiting.

Terminal window
git status

tells you which operation is in progress and what it wants. Every one of them has an abort:

Terminal window
git rebase --abort
git merge --abort
git cherry-pick --abort

The safe default when confused is to abort. It returns you to where you started. Ask questions from a clean state rather than from halfway through an operation you do not understand.

See merge conflict resolution for resolving rather than aborting, including the rebase inversion that makes --ours and --theirs mean the opposite of what you expect.

Committed to main instead of a feature branch. Common, and entirely recoverable.

The safe shape: create the branch you meant to be on at the current commit, then move main back.

Terminal window
git switch -c feature/my-work
git switch main
git reset --hard origin/main

The second command is destructive to local main and safe here only because origin/main is the state you want and your work is now on feature/my-work. That conditional is exactly the kind of thing to confirm before running — and exactly what a suggested command will not state.

fatal: Could not read from remote repository.
Please make sure you have the correct access rights

What it means. Ambiguous by design — wrong credentials, no credentials, no access, or the repository does not exist.

Where AI helps. Narrowing it, given git remote -v and whether you are on SSH or HTTPS.

Where to be careful. Suggestions to change your credential configuration. See Git credentials and SSH keys — and never paste a token or a private key into a chat while debugging.

Not an error, and a frequent source of “what is Git telling me”.

Terminal window
git status

The states that confuse people, and what each means:

Status saysMeans
Changes not staged for commitModified in the working tree, not added
Changes to be committedStaged; the next commit includes these
Untracked filesGit has never seen these
Your branch is ahead of 'origin/main' by 2 commitsYou have commits to push
Your branch and 'origin/main' have divergedBoth moved; you need to merge or rebase
both modified / UUAn unresolved conflict
HEAD detached at <hash>Not on a branch

Pasting git status and asking “what does this mean, and what are my options?” is a genuinely good use of AI — it is a reading-comprehension question about unfamiliar output with a checkable answer, and the answer names the state rather than proposing a change.

The follow-up that keeps it safe: ask for the options and their consequences rather than for the command to run.

Occasionally the problem is not an error but a repository behaving badly — a clone that takes twenty minutes, a git status that takes ten seconds.

The diagnostics are read-only and safe to run:

Terminal window
{/* Object counts and pack statistics */}
git count-objects -vH
Terminal window
{/* Repository size on disk */}
du -sh .git

Common causes worth knowing before asking: large binaries committed at some point in history, a missing .gitignore letting build output in, or a repository that has never been repacked.

The remedies range from harmless (git gc) to history-rewriting (removing large objects), and the second category belongs to the same rules as removing a secret — including coordinating with everyone who has a clone. See also Git maintenance.

A rough classification worth internalising, because it determines how much verification a suggestion needs.

RiskCommandsBefore running
Safestatus, log, diff, reflog, show, branch -vvNothing. Run them freely
Reversibleswitch, checkout <branch>, stash, switch -cGlance at git status
Local rewritereset, rebase, commit --amend, stash dropKnow what you lose; note your reflog position
Shared destructionpush --force, push --delete, branch -D on shared workUnderstand fully, or do not run it

The read-only row is where AI diagnosis should spend most of its time. A useful framing when asking:

Give me read-only commands to diagnose this first. Do not suggest anything that changes state until I have shown you the output.

That single instruction converts the interaction from “here is a fix” to “here is a diagnosis”, which is the correct order and the one that produces fewer accidents.

Why AI is unusually good at this, and unusually risky

Section titled “Why AI is unusually good at this, and unusually risky”

Both halves are worth understanding, because they come from the same property.

Good, because Git’s errors are dense and standardised. The messages have not changed much in years, the states are finite, and the diagnostic commands are the same every time. That is close to ideal input: a well-defined problem space with abundant public discussion of every failure mode.

Risky, because the person asking cannot evaluate the answer. Everywhere else in this cluster you can check the output — a commit message against a diff, a summary against the commits it cites. Here, the whole reason you are asking is that you do not know what is happening, which means you cannot tell a correct fix from a plausible one.

That asymmetry is why this article is structured around process rather than answers. The five inputs, the read-only-first instruction, the backup branch and the risk table are all mechanisms for being safe while uncertain — because “verify the answer” is not available to you in the moment it matters.

The corollary worth carrying: the time to learn Git’s model is not while something is broken. The fundamentals and workflows pillars are what make you able to evaluate a suggestion, and AI assistance is a supplement to that understanding rather than a replacement for it.

Before any operation in the bottom two rows:

Terminal window
{/* A branch pointing at the current state, in case you need to come back */}
git branch backup/before-rebase

That is one command, it creates a named reference to your current commit, and it makes the entire operation reversible with git reset --hard backup/before-rebase.

For the working tree as well:

Terminal window
git stash push -u -m "before troubleshooting"

Neither is sophisticated. Both remove the consequence from the category of decisions you are making while confused, which is the category you should be trying to empty.

Copilot CLI can run the diagnostic commands itself rather than asking you to paste output. For troubleshooting specifically, that changes the ergonomics substantially — gathering status, log, reflog and branch -vv is most of the work, and an agent doing it produces a much better-informed first answer.

It also removes the friction that was doing useful work. Pasting output is slow, and slowness is what makes you read it.

The configuration that keeps the benefit without the risk:

Allow the read-only commands. git status, log, diff, reflog, show, branch are safe to run without asking, and letting them run makes diagnosis fast.

Approve state-changing commands individually. Every reset, rebase, checkout and push should be an explicit decision, especially while you are confused about the state.

Never use --allow-all-tools for troubleshooting. This is the exact scenario the approval prompt exists for. You are in an unfamiliar state, and the commands that resolve unfamiliar states are the destructive ones.

The general principle from the CLI cluster applies with unusual force here: generating a command is research; running it is a decision. Troubleshooting is when that distinction is easiest to lose, because you want the problem to be over.

Some situations are worth escalating rather than debugging, and recognising them early is cheaper than recovering afterwards.

Anything involving a shared branch’s history. If the fix involves force-pushing something other people have pulled, that is a conversation before it is a command — see when not to rebase.

Anything where you cannot state what you would lose. The rule at the top of this page. Not knowing is a reason to ask, not a reason to try.

Anything in a repository you cannot afford to break. The calculus changes when there is no disposable copy.

Anything where you have already tried three suggested fixes. Compounding attempts on a state you did not understand initially is how a recoverable problem becomes an unrecoverable one. Stop, take a backup branch, and describe the whole sequence to somebody.

That last one is worth watching for in yourself. The failure pattern is not one wrong command; it is four, each responding to the mess made by the last, with the reflog getting longer and the mental model getting worse.

Pasting the error without the hints. The hints usually contain the answer.

Omitting what you were trying to do. The same output has different remedies depending on intent.

Running a suggested --force push. Use --force-with-lease, and only after establishing whose commits are on the remote.

Believing commits are gone. Check the reflog first. They almost never are.

Continuing a rebase you do not understand. --abort returns you to a known state.

Not taking a backup branch. One command, and it makes the next mistake recoverable.

Pasting credentials while debugging authentication. The error text is enough; the token is not needed.

Accepting a reset --hard because the explanation was fluent. The explanation is generated by the same system as the command.

Putting the process together on a real situation: you rebased a branch, something went wrong, and you are not sure what state you are in.

Step 1 — gather, do not act.

Terminal window
git status
git log --oneline -5
git branch -vv
git reflog | head -20

Four read-only commands. Nothing has changed.

Step 2 — describe the goal, not the command.

I was rebasing feature onto main. Something went wrong and I am not sure what state I am in. Here is git status, git log --oneline -5, git branch -vv and the last 20 reflog entries. What state am I in, and what are my options? Do not suggest anything that changes state yet — I want to understand first.

Step 3 — check the diagnosis yourself. The answer should name a state you can confirm: mid-rebase, detached, diverged, clean. git status says which; if the diagnosis does not match what status says, stop and re-ask.

Step 4 — take the backup.

Terminal window
git branch backup/pre-fix

Step 5 — ask for options with consequences.

Given that state, what are my options, and what does each one lose?

Step 6 — pick, then confirm you can state the effect. If you cannot, ask what the command does before running it.

Step 7 — verify. git log --oneline -5 and git status again. Did the result match the prediction? If not, git reset --hard backup/pre-fix puts you back.

Seven steps, of which five are read-only and one is a single-command backup. The whole process takes two minutes and turns an unfamiliar state into a reversible decision — which is the entire objective.

Git rarely loses anything; it makes things unreachable. Most troubleshooting is finding the reference you lost rather than recovering data — which is why the safe commands are the diagnostic ones, and why the reflog is the first place to look rather than the last.

  • Supply the command, the full output, git status, branch state, and your intent — all five
  • Git’s hint: lines usually contain the remedy, and are the part people trim
  • git branch -vv shows tracking and divergence, which is most push confusion
  • Detached HEAD is recoverable with git switch -c; nothing is broken
  • The reflog records every HEAD position, and is how “lost” commits are found
  • --force-with-lease refuses when the remote has moved; --force does not
  • Every interrupted operation has an --abort that returns you to a known state
  • Ask for read-only diagnostics first, and share the output before accepting a fix
  • A backup branch is one command and makes the next step reversible

Use a disposable repository. Break it deliberately.

  1. Commit twice, then git checkout HEAD~1. Read the detached HEAD message. Predict: are your commits still reachable?

  2. Commit while detached, then git switch main. Predict: where did that commit go? Find it with git reflog.

  3. Recover it onto a branch with git switch -c recovered <hash>.

  4. Clone your repository to a second directory. Commit in both, push from one, then push from the other. Read the full rejection including hints. Predict: what does git branch -vv show?

  5. Ask an assistant for a fix, giving only ! [rejected]. Then ask again with the full output plus status and branch -vv. Compare.

  6. Run git reset --hard HEAD~2, then recover using the reflog. Predict: what does the reflog entry for the reset look like?

  7. Try git push --force-with-lease on a branch whose remote has moved. Predict: does it refuse?

  8. Delete both copies.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.