· 6 min read

Unstaging Files in Git: Why `git restore --staged` Fails with `fatal: could not resolve HEAD`

Two commands seem to unstage files in Git `git rm --cached` and `git restore --staged`. One throws `fatal: could not resolve HEAD` on a fresh repo. Here's why, traced step by step.

A student ran git restore --staged notes.txt on a brand-new repo yesterday and got this:

fatal: could not resolve HEAD

The same command works fine on every other repo they’ve touched. So why does it blow up here, and what should they have run instead?

This post walks through both cases fresh repo and repo with commit history and traces what happens in each of Git’s three areas at every step. By the end, the error message should read like a sentence, not a riddle.

TL;DR

CommandWhat it actually doesWhen to use it
git rm --cached <file>Removes the file from the index. Working tree untouched.Unstaging a file that has never been committed.
git restore --staged <file>Copies the file’s content from the last commit back into the staging area.Unstaging a file in a repo that has at least one commit.

The second command needs a “last commit” to copy from. No commit → no source → error.

Git’s three areas (quick refresher)

Every file in a Git project moves between three places:

AreaWhat lives hereStandard Git name
Working areaThe file as it exists on disk right now.working tree
Staging areaThe snapshot you’ve marked “ready for the next commit.”index
Commit areaThe latest snapshot you actually saved.HEAD (the latest commit)

Movement between them:

                  git add                  git commit
   Working area ─────────▶  Staging area ─────────▶  Commit area

                                  │  git restore --staged
                                  │  (copies FROM Commit area)

                                  └────────────────────

git restore --staged is essentially the reverse arrow of git commit: it pulls from the commit area back into the staging area. Hold onto that the error message falls out of this one fact.

Scenario A —> Fresh repo, no commits yet

You just ran git init. There is no commit history. You create notes.txt with the text Hello World.

Working area  →  Hello World
Staging area  →  (empty)
Commit area   →  (empty — no commits yet)

Stage it:

git add notes.txt
Working area  →  Hello World
Staging area  →  Hello World
Commit area   →  (empty)

Now you change your mind. You want to unstage. Two commands seem to fit. Try the more familiar one first:

git restore --staged notes.txt
# fatal: could not resolve HEAD

Park that error for a moment. Try the other one:

git rm --cached notes.txt
Working area  →  Hello World      (your file is still here)
Staging area  →  (empty)
Commit area   →  (empty)

That worked. The file is back to being untracked exactly the state you were in before git add. No data lost.

But why did git restore --staged fail? To answer that, we need to see what it does when it succeeds.

Scenario B —> Repo with existing commits

Same file, but this time you committed once first.

Working area  →  Hello
Staging area  →  Hello
Commit area   →  Hello       (committed once)

Now edit the file to Hello world and stage the change:

# After editing the file on disk
git add notes.txt
Working area  →  Hello world
Staging area  →  Hello world
Commit area   →  Hello       (unchanged —> the commit is still the old version)

Three different states across three areas. Unstage with:

git restore --staged notes.txt

Here is the exact question Git asks itself:

What should the staging area look like after this command?

Its answer is always the same:

It should look exactly like the last commit.

So Git copies the content of notes.txt from the last commit back into the staging area:

Working area  →  Hello world      (still has your new edit)
Staging area  →  Hello            (reset to match the last commit)
Commit area   →  Hello

Notice what did not happen: your working file is untouched. You still have Hello world on disk. Only the staged version was rolled back.

That’s git restore --staged in one sentence: copy from the last commit into the staging area.

Back to the error

Now Scenario A makes sense. On a fresh repo, when you ran git restore --staged notes.txt:

Working area  →  Hello World
Staging area  →  Hello World
Commit area   →  ???              ← there is no last commit

Git asked the same question “what should the staging area look like?” and the answer is the last commit. But there is no last commit. HEAD doesn’t point at anything yet. So Git gives up:

fatal: could not resolve HEAD

It’s not a bug. It’s Git refusing to copy from a source that doesn’t exist.

Watch out: git rm --cached is not a general-purpose unstage

This is the trap. git rm --cached works for “unstaging” only in the fresh-repo case. On a file that has already been committed, the same command does something completely different it stages the file’s deletion:

# notes.txt is already committed
git rm --cached notes.txt
git status
# Changes to be committed:
#   deleted:    notes.txt
# Untracked files:
#   notes.txt

If you commit after that, Git will record a deletion of the file in the repo (the file stays on your disk, but the next commit will say “this file is gone”). That is almost never what someone trying to “unstage” actually wants.

So the rule is sharper than “use whichever”:

  • No commits yetgit rm --cached is the right tool. git restore --staged cannot work here.
  • At least one commitgit restore --staged is the right tool. Do not use git rm --cached unless you genuinely want to stage a deletion.

Try this yourself

In a scratch folder:

  1. git init a new repo. Create a file, git add it, then try git restore --staged — watch it fail. Read the full error message.
  2. Unstage the same file with git rm --cached. Confirm with git status that the file is back to untracked.
  3. Now git add it again and git commit once.
  4. Edit the file, git add again, then git restore --staged. Watch it succeed. Confirm with git diff (working vs staging) and git diff --staged (staging vs commit) what actually got rolled back.
  5. Finally, try git rm --cached on the now-committed file. Read git status carefully — you’ll see deleted: rather than an unstage. That’s the trap to remember.

If you can predict git status between every step, you understand the staging area better than most working Git users.

Cohort Notes — monthly

What we shipped, what the cohort is building, and when the next batch starts. One short email a month. No spam.