Git Checkout Remote Branch: The Reliable Way to Pull Down Any Branch
Someone pushed a branch called `feature-x` to your shared repository. You want it on your machine so you can run it, review it, or build on top of it. You type `git checkout feature-x` and Git fires back:
“` error: pathspec ‘feature-x’ did not match any file(s) known to git “`
The branch exists. Your colleague is staring at it on GitHub right now. So why does your local Git insist it doesn’t exist? This article walks through exactly how to check out a remote branch, why that error happens, and the two-step workflow that works every single time.
Key Takeaways
• You cannot check out a remote branch your local Git has never heard of. Run `git fetch` first so Git learns what exists on the remote.
• The modern, simple way is `git fetch` then `git checkout branchname` — Git auto-creates a local tracking branch for you.
• `git switch branchname` does the same thing with clearer, safer semantics in recent Git versions.
• Checking out a remote-tracking ref directly (`git checkout origin/feature-x`) lands you in detached HEAD — usually not what you want.
• A tracking branch remembers its upstream, so plain `git pull` and `git push` just work.
Why does “git checkout remote branch” fail with “pathspec did not match”?
This is the single most common point of confusion, so let’s settle it first.
Git is distributed. Your local repository keeps its own snapshot of what it *thinks* exists on the remote. That snapshot only updates when you explicitly ask for it. If a teammate pushed `feature-x` ten minutes ago and you haven’t synced since, your local Git literally has no record that `feature-x` exists. So `git checkout feature-x` finds nothing to match — hence `pathspec ‘feature-x’ did not match`.
The most common “git checkout remote branch” confusion is that you cannot check out a branch your local Git has never *heard* of. Remote branches only become checkout-able after a `git fetch` updates your local list of what exists on the remote. People try `git checkout feature-x`, get “pathspec did not match,” and assume the branch is gone — when really their local repo just hasn’t fetched it yet. The reliable two-step that always works: `git fetch` (learn what’s on the remote), then `git checkout feature-x` (modern Git auto-creates a tracking branch). Fetch makes the remote’s branches visible; checkout adopts one locally. Miss the fetch and even an existing branch looks invisible.
“`bash
git checkout feature-x
git fetch origin git checkout feature-x
“`
That’s the whole trick. Almost every other problem in this article is a variation on “you skipped the fetch.”
How do I check out a remote branch the modern, simple way?
Modern Git (version 2.23 and later) makes this nearly automatic. As long as the branch name is unique across your remotes, you don’t need to spell out the remote or create the local branch by hand.
“`bash git fetch origin git checkout feature-x “`
Git sees that `feature-x` doesn’t exist locally but *does* exist as `origin/feature-x`, and it does the right thing: creates a local branch named `feature-x`, sets its upstream to `origin/feature-x`, and switches you onto it. This is called DWIM (“Do What I Mean”) behavior.
If you prefer the newer, purpose-built command, `git switch` works identically for this case:
“`bash git fetch origin git switch feature-x “`
`git switch` was introduced specifically to separate “change which branch I’m on” from `git checkout`, which is historically overloaded (it also restores files, which has caused plenty of accidental data loss). For branch work, prefer `switch`. For a deeper look at how the older command behaves across its many modes, see our guide to git checkout.
What’s the explicit way to check out a remote branch?
The DWIM shortcut only works cleanly when the branch name exists on exactly one remote. If the same name lives on multiple remotes, or you want a *different* local name, be explicit:
“`bash
git checkout -b my-feature origin/feature-x
git switch -c my-feature origin/feature-x “`
Read these as: “create a new local branch (`-b` / `-c`) called `my-feature`, starting from and tracking `origin/feature-x`.” The `-c` in `switch` stands for *create*; the `-b` in `checkout` stands for *branch*. Both set up tracking automatically.
This explicit form is what you reach for when:
- The branch name is ambiguous across multiple remotes.
- You want your local branch to have a clearer or different name.
- You’re scripting and want zero reliance on DWIM guesses.
How do I list remote branches before checking one out?
You can’t check out what you can’t see. After a fetch, two commands show you what’s available.
“`bash
git branch -r
git branch -a “`
Typical output of `git branch -a`:
“` * main develop remotes/origin/main remotes/origin/develop remotes/origin/feature-x “`
Anything prefixed with `remotes/origin/` is a remote-tracking ref — a read-only local mirror of where that branch sat on the remote at your last fetch. Those are the candidates you can adopt into a real local branch. If a branch you expect is missing from this list, fetch again — your mirror is stale.
“`bash git fetch –all –prune git branch -a “`
The `–prune` flag cleans out remote-tracking refs for branches that have since been deleted on the remote, so your list stays honest.
What does “tracking branch” actually mean?
A tracking branch (more precisely, a local branch with an *upstream*) is a local branch that remembers which remote branch it corresponds to. That relationship is what makes the bare `git pull` and `git push` commands work without arguments.
“`bash git checkout feature-x # creates a tracking branch automatically git pull # knows to pull from origin/feature-x git push # knows to push to origin/feature-x “`
Without an upstream set, Git makes you spell out the remote and branch every time, and may refuse to push at all. You can inspect or set the relationship directly:
“`bash
git branch -vv
git branch –set-upstream-to=origin/feature-x “`
For the mechanics of moving commits between your tracking branch and the remote, see our walkthroughs of git pull and git fetch — the two halves of staying in sync.
Goal-to-command reference
| Goal | Command |
|---|---|
| See what’s on the remote | `git fetch origin` |
| List remote-tracking branches | `git branch -r` |
| List all branches (local + remote) | `git branch -a` |
| Check out a remote branch (modern) | `git checkout feature-x` |
| Check out a remote branch (switch) | `git switch feature-x` |
| Check out under a different local name | `git checkout -b my-name origin/feature-x` |
| Same, with switch | `git switch -c my-name origin/feature-x` |
| See what each local branch tracks | `git branch -vv` |
| Update your current branch | `git pull` |
| Prune deleted remote branches | `git fetch –all –prune` |
How do I check out someone’s pull request or shared branch?
Reviewing a colleague’s work follows the exact same fetch-then-checkout pattern. Say they pushed `bugfix/login-crash`:
“`bash git fetch origin git switch bugfix/login-crash
“`
For pull requests on hosting platforms, you can fetch the PR ref directly. On GitHub, for example, every PR exposes a `refs/pull/
“`bash
git fetch origin pull/42/head:pr-42 git switch pr-42 “`
This is invaluable for code review or reproducing a reported bug exactly as the author has it. When you’re done, switch back and delete the throwaway branch:
“`bash git switch main git branch -D pr-42 “`
What is the detached-HEAD trap, and how do I avoid it?
Here’s a mistake that bites everyone once. You try to “check out the remote branch” by naming the remote-tracking ref directly:
“`bash git checkout origin/feature-x “`
Git obeys — but instead of a normal branch checkout, it drops you into detached HEAD:
“` Note: switching to ‘origin/feature-x’.
You are in ‘detached HEAD’ state. You can look around, make experimental changes and commit them… “`
Why this happens: `origin/feature-x` is a *remote-tracking ref*, not a local branch. It’s a pointer that Git considers read-only — it moves only when you fetch. When you check it out directly, Git has no local branch to attach `HEAD` to, so `HEAD` points straight at a commit. Any commits you make now belong to no branch. Switch away and they become unreachable, easy to lose, and a pain to recover.
The fix is to check out the branch *name* (which creates a proper local tracking branch) rather than the remote-tracking ref:
“`bash
git checkout origin/feature-x
git checkout feature-x
git switch -c feature-x origin/feature-x “`
If you’ve *already* committed in detached HEAD and panicked, don’t. Create a branch from where you are before moving:
“`bash git switch -c rescue-branch “`
That captures your detached commits onto a named branch so nothing is lost. The rule of thumb: never name `origin/anything` as your checkout target unless you deliberately want a read-only, look-around state.
Run Git in a real environment you control. Local checkouts are only half the story — eventually you deploy. DarazHost VPS and dedicated servers give developers full SSH and Git access directly on the server: fetch and check out remote branches, set up tracking branches, and run git-based deploys in your real production environment with full root control and 24/7 support. No throttled shells, no missing binaries — the same workflow on your machine and your server. Read more in the complete guide to a real developer hosting environment you control.
How do I keep a checked-out remote branch up to date?
Once your local tracking branch exists, staying current is one command. Because the branch knows its upstream, `git pull` needs no arguments:
“`bash git switch feature-x git pull “`
`git pull` is really `git fetch` followed by an integration step (`merge` by default, or `rebase` if you configure it). If you only want to *see* what changed on the remote without altering your working branch yet:
“`bash git fetch origin git log HEAD..origin/feature-x –oneline # what’s on the remote that you don’t have “`
A common, conflict-friendly pattern before pushing your own work is to rebase onto the freshly fetched upstream:
“`bash git fetch origin git rebase origin/feature-x “`
This replays your local commits on top of the latest remote state, keeping history linear. Whichever you choose, the prerequisite never changes: fetch first so Git knows the current state of the remote.
Frequently asked questions
Why does `git checkout feature-x` work for my teammate but not me? Your teammate has fetched more recently. Their local Git knows `feature-x` exists; yours doesn’t yet. Run `git fetch origin`, then retry. The branch was never missing — your local mirror was just out of date.
What’s the difference between `git checkout` and `git switch` for remote branches? For checking out a branch, they’re functionally equivalent. `git switch` is newer and does *only* branch switching, so it can’t accidentally clobber files the way overloaded `git checkout` can. Use `switch` for branch work and `checkout` only when you need its file-restoration modes.
Do I have to create the local branch myself with `-b`? No, not in modern Git. If the remote branch name is unique, `git checkout feature-x` auto-creates the local tracking branch. Use `-b` / `-c` only when the name is ambiguous across remotes or you want a different local name.
My branch shows in `git branch -r` but `git checkout` still fails — why? Almost always a typo or a slash in the name. Remote-tracking refs appear as `origin/feature-x`; the branch name you check out is just `feature-x` (no `origin/`). Copy the part after the last `origin/` exactly, including any `feature/` path segments.
How do I check out a remote branch without setting up tracking? Use `–no-track`: `git checkout –no-track -b local-name origin/feature-x`. You’ll then need to specify the remote and branch explicitly on every push and pull, since there’s no upstream recorded.
The bottom line
Every reliable “git checkout remote branch” workflow starts with one step people skip: fetch. Once Git knows what lives on the remote, `git checkout branchname` (or `git switch branchname`) adopts it as a local tracking branch, and `git pull` keeps it current. Avoid checking out `origin/branch` directly unless you genuinely want a read-only detached state. Internalize the two-step — fetch, then checkout — and “pathspec did not match” stops being a mystery.