Git Pathspec Did Not Match — Fix It Fast
Check your directory and spelling to fix pathspec errors, then fetch remote branches before you switch or restore the path..
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
- ✓Basic git add, commit, and branch commands
- ✓A cloned repo with a remote configured
- ✓Comfort running commands in the terminal
- fatal: pathspec did not match means Git can't find the branch, file, or path you named in the current repo state.
- Fix typos first: run git branch -a and git status to compare your spelling against real branch and file names.
- If you're in the wrong folder or the branch is remote-only, cd to the repo root and run git fetch --all --prune first.
- Use git switch for branches and git restore for files; sparse-checkout users must check git sparse-checkout list for hidden paths.
Think of Git as a librarian who only fetches books that are on the shelf list. If you ask for Harry Potter and the Sorceror's Stone with a typo, or you ask for a book that's still in the delivery truck, she says she can't find it. The pathspec error is that polite refusal. You either misspelled the title, you're standing in the wrong library branch, or the book hasn't been shelved yet. Fix the name, go to the right room, or unload the truck first.
You type git checkout feature-login, hit enter, and Git answers: error: pathspec 'feature-login' did not match any file(s) known to git. Or you run git add src/App.tsx and get the same refusal for a file you swear exists. Nothing is broken, yet nothing moves forward.
The reflex is to retry with sudo, re-clone the repo, or force-push something. None of that helps, because the error is about naming and visibility, not permissions. Git searched its current index of branches and files, didn't find your string, and stopped rather than guess.
Five causes cover nearly every case: a typo in the branch or path, running the command from the wrong directory, a branch that exists only on the remote and was never fetched, mixing up git switch and git checkout semantics, and sparse-checkout hiding the path from your working tree.
This guide shows how to read which cause you've hit, the exact commands that confirm it, and the minimal fix for each. You'll clear the error in under two minutes and stop re-cloning repos to solve a spelling problem.
Typo'd Branch or Path: Read the Error Closely
Most pathspec failures are spelling errors wearing a scary message. feature-login versus feature_logins, src/App.tsx versus src/app.tsx on a case-sensitive filesystem, release-2.14 versus release-2.14.1 — Git does fuzzy nothing. It compares your string byte for byte against known refs and paths, and one wrong character is a total miss. The error helpfully echoes your string back; compare that echo to reality instead of retyping the same typo.
Confirm with two read-only commands before changing anything. git branch -a shows every local and remote-tracking branch Git currently knows, and git status --short shows tracked and untracked paths in your working tree. Pipe the first through grep with a fragment you trust: git branch -a | grep login surfaces every login-flavored branch so you can pick the exact name. For files, ls the directory and use tab completion rather than memory.
Case trips up teams that mix macOS and Linux. macOS checkouts often behave case-insensitively while CI runs case-sensitively, so App.tsx works on your laptop and fails in the pipeline. Globs trip up the shell before Git ever sees them: unquoted src/*.tsx expands locally and vanishes if nothing matches. Quote your patterns.
Make the fix boring: copy the exact name from branch -a or ls output, paste it into your command, and verify with git rev-parse --verify <name>. You'll clear the majority of pathspec reports right here.
Wrong Directory: You're Not Where You Think
Git resolves relative pathspecs against your current directory, not the repo root. Running git add src/App.tsx from repo-root works; running it from repo-root/src fails because Git looks for src/src/App.tsx. Running any branch command from outside the repo fails differently but just as confusingly. Monorepos multiply the risk: packages/api versus apps/api look alike until you're in the wrong one.
Diagnose location before syntax. pwd tells you where the shell is, git rev-parse --show-toplevel tells you where the repo root is, and git status tells you which directory's changes Git sees. If those disagree with your assumption, you've found the bug. Nested repos and worktrees add a twist: git rev-parse --git-dir reveals which .git you're actually talking to when folders contain more than one checkout.
The fix is to anchor yourself. cd to the output of git rev-parse --show-toplevel and rerun the command, or prefix with git -C <root> to run from the root without moving: git -C ~/code/app status. For scripts, use absolute repo-root-relative paths or :/ prefixes that anchor to the root regardless of cwd: git add :/src/App.tsx works from any subdirectory.
Add pwd and git rev-parse --show-toplevel to your deploy logs. That two-line context has resolved more pathspec mysteries than every Stack Overflow thread combined.
Unpushed and Remote-Only Branches: Fetch First
A branch that exists on GitHub but was never fetched doesn't exist for your local Git. Fresh clones fetch only the default branch plus remote-tracking refs at clone time; branches created afterward are invisible until git fetch updates your remote-tracking list. Deleting a local branch while it still exists remotely creates the mirror problem: you see origin/feature but no local feature, and bare git checkout feature may or may not guess the tracking setup depending on your config.
Confirm visibility with git branch -a, which lists both local branches and origin/* remote-tracking refs, and git ls-remote origin to see what's on the server without fetching. If ls-remote shows the branch but branch -a doesn't, you're simply behind: fetch. If both show it but checkout still fails, your spelling or your checkout.mode config is the culprit, not the network.
Fetch deliberately, then track explicitly. git fetch --all --prune refreshes every remote and drops stale refs that confuse guessing. Then git switch --track origin/feature-login creates the local branch with its upstream set, which beats git checkout's sometimes-magical guessing. For one-shot checkouts, git fetch origin feature-login:feature-login creates the local ref directly.
Clone flags matter at scale. Pipelines that use --depth 1 or --single-branch to save time can't see other branches by design. Either fetch with --unshallow or widen the clone when you need full ref access.
Git Switch vs Checkout: Use the Right Tool
git checkout does three jobs — switch branches, restore files, and create branches — and guesses which one you meant. That guessing is a classic pathspec source: git checkout report.txt tries to switch to a branch named report.txt before it tries to restore the file, and fails with pathspec when no such branch exists. Modern Git split the jobs: git switch moves branches, git restore recovers files, and checkout remains as the overloaded legacy.
Use the split commands and the ambiguity vanishes. git switch feature-login can only mean a branch, so a typo there is unambiguously a branch problem. git restore --source=main -- src/App.tsx can only mean a file, so a typo there is unambiguously a path problem. Each error message gets shorter and more accurate because the command already declared its intent.
When you must use checkout, disambiguate with --. The double dash tells Git everything after it is a path: git checkout main -- src/App.tsx checks out the file from main instead of hunting for a branch named App.tsx. Without the separator, branch names that look like paths (or paths that look like branches) trigger guess failures that read exactly like typos.
Teach your fingers the new pair and alias the old habit away. switch plus restore covers every checkout use with clearer errors, and your future pathspec reports will name the right category on the first line.
Sparse-Checkout Hiding Paths: Files You Can't See
Sparse-checkout lets a monorepo show only part of the tree in your working copy. Files outside the cone still exist in history, but your disk doesn't have them and pathspec commands that name them fail as if you'd typo'd. git checkout main -- services/billing/api.ts errors when your cone covers only apps/web, even though the path is perfectly valid in the repo. The confusion peaks because git ls-tree shows the file while ls doesn't.
Check the cone before the spelling. git sparse-checkout list prints the directories Git materializes, and git sparse-checkout status shows whether the feature is enabled. Compare that list to your target path: if the path falls outside every cone entry, no spelling fix will help until you widen the cone. git check-ignore won't explain this one — the file isn't ignored, it's just not checked out.
Widen deliberately. git sparse-checkout add services/billing pulls one more directory into the working tree without disabling the feature. git sparse-checkout set with a new pattern list replaces the cone when you're re-scoping. git sparse-checkout disable restores the full tree when you need everything, at the cost of disk and checkout time on large repos.
Document cone requirements in the repo readme. When onboarding says this service needs apps/web plus services/billing, every pathspec report from a fresh sparse clone answers itself.
Stale Refs and Deleted Branches: Prune the Ghosts
Remote branches get deleted after merges, but your local remote-tracking refs linger until pruned. Then git branch -a shows origin/feature-login, you try to switch, and the fetch you just ran deleted that tracking ref because the upstream is gone — pathspec on a branch you can still see in old output. The reverse haunts restores: a file deleted on main still exists in your head's history, and checking it out from the wrong ref fails.
Treat ref lists as perishable. git fetch --prune (or --all --prune for every remote) deletes local tracking refs whose upstream vanished, so branch -a reflects the server instead of last month. git branch -vv shows which local branches track deleted upstreams with a gone marker — those need deletion or re-pointing, not checkout retries. git remote prune origin does the same cleanup when you want it without fetching.
For files, confirm existence on the exact ref you're reading from. git ls-tree -r main --name-only | grep App.tsx proves the path lives on main right now; without that proof you're restoring from memory. Deleted files restore fine from the commit before deletion with git restore --source=HEAD~1 -- <path>, but only once you've named a ref that actually contains them.
Schedule pruning so ghosts don't accumulate. A fetch with prune in your daily pull habit, plus gone-branch cleanup monthly, keeps every pathspec report about real names instead of deleted ones.
The Typo'd Deploy Branch That Delayed Release 40 Minutes
- Never hardcode branch names in scripts: take them from variables and verify with rev-parse before checkout so typos fail with helpful output.
- Print pwd, git branch -a, and git status at the start of deploy steps. That context turns a 40-minute mystery into a 2-minute spelling fix.
- Treat pathspec as a naming error first, not an infrastructure error. Compare your string to real refs before clearing caches or rebuilding runners.
| File | Command / Code | Purpose |
|---|---|---|
| git branch -a | grep -i login | Typo'd Branch or Path | |
| pwd | Wrong Directory | |
| git branch -a | grep release | Unpushed and Remote-Only Branches | |
| git switch main | Git Switch vs Checkout | |
| git sparse-checkout status | Sparse-Checkout Hiding Paths |
Key takeaways
Common mistakes to avoid
5 patternsRetyping the same typo harder instead of listing refs
Running path commands from a subdirectory
Assuming clone fetched every branch
Using checkout for files without the -- separator
Forgetting sparse-checkout cones on monorepos
Interview Questions on This Topic
What does fatal: pathspec did not match mean?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Everything here is grounded in real deployments.
That's Git. Mark it forged?
6 min read · try the examples if you haven't