fatal: not a git repository (or any of the parent directories): .git - 10 Causes and Fixes
You ran a Git command and got fatal: not a git repository (or any of the parent directories): .git. That message means exactly one thing: Git walked up from your current directory to the filesystem root and never found a .git entry, so it has no repository to operate on.
This is not a corruption error and not a network error. It is a location error — either you are standing in the wrong directory, or the directory you expected to be a repository never became one, or the .git directory is there but unreadable by the process that asked.
Below: the one-line fix that covers most cases, then 10 distinct causes with a reproduction and a fix for each, a diagnostic checklist for the cases that match none of them, and an explanation of why Git is designed to fail this way.
| Error | fatal: not a git repository (or any of the parent directories): .git |
|---|---|
| Where it happens | Git CLI on any OS (Linux, macOS, Windows, WSL), and anything that shells out to git: IDE Git panels, CI runners, Docker builds, npm/husky hooks, deploy scripts. |
| What it means | Git searched the current directory and every parent directory for a .git entry, found none, and refused to guess which repository you meant. |
The Fast Fix
Most of the time you are simply not inside the repository. Check where you are, then move into the repo or create one.
pwd # where am I?
git rev-parse --show-toplevel # prints repo root, or the same fatal error
cd /path/to/your/repo # case A: you were outside it
git init # case B: this folder is genuinely not a repo yet
If git rev-parse --show-toplevel prints a path, you are inside a repository and your original command failed for a different reason — read the error again carefully. If it repeats the fatal message, you need one of the causes below.
What Is Actually Causing It
Jump to your case
- 1. You are in the wrong directory
- 2. The directory was never initialised as a repository
- 3. The clone failed, so the target directory is empty or absent
- 4. You are one level above the repository root
- 5. The .git directory was deleted or renamed
- 6. The repository is on a filesystem Git refuses to trust (dubious ownership)
- 7. GIT_DIR points at a path that does not exist
- 8. A submodule or worktree .git file points at a missing location
- 9. The command runs from a different directory than you think (scripts, hooks, CI)
- 10. A Docker build or container never received the .git directory
1. You are in the wrong directory
Reproduce it
cd ~
git status
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — Git resolves the repository from the current working directory upward. Your home directory has no .git, and neither does / above it, so the search ends in failure. A new shell tab, a cd you forgot, or a terminal that opens in $HOME by default all land you here.
The fix
cd ~/projects/my-app
git status
# On branch main
What changed: nothing about the repo — only the directory the command runs from.
Confirm it worked — git rev-parse --show-toplevel prints the repository root path instead of the fatal error.
2. The directory was never initialised as a repository
Reproduce it
mkdir new-project && cd new-project
git add .
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — Creating a folder does not create a repository. Git stores all of its state in a .git directory, and until git init (or a clone) creates it, the folder is an ordinary directory with no history, no index, and no config.
The fix
git init
git add .
git commit -m "Initial commit"
What changed: git init created the .git directory that every later command needs.
Confirm it worked — ls -a lists .git, and git status reports untracked files rather than failing.
3. The clone failed, so the target directory is empty or absent
Reproduce it
git clone https://example.com/does-not-exist.git app
# fatal: repository 'https://example.com/does-not-exist.git/' not found
cd app 2>/dev/null || mkdir app && cd app
git log
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — When a clone aborts — bad URL, auth failure, dropped connection — Git removes or never finishes the target directory. You then cd into a leftover empty folder (or one you created by hand) and every command fails because the clone never wrote .git.
The fix
cd ..
rm -rf app
git clone https://github.com/your-org/your-repo.git app
cd app
git log --oneline -1
What changed: the clone was re-run with a URL that resolves, and the half-made directory was removed first so the clone had a clean target.
Confirm it worked — git log --oneline -1 prints a commit, and git remote -v lists the origin URL.
4. You are one level above the repository root
Reproduce it
git clone https://github.com/your-org/your-repo.git
# Cloning into 'your-repo'...
git status # still in the parent directory
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — Git's search goes upward only, never downward into subdirectories. The repository exists in ./your-repo, but from the parent directory Git never looks inside it, so it reports failure even though the repo is a few characters away.
The fix
cd your-repo
git status
# On branch main
What changed: you descended into the clone. Alternatively run git -C your-repo status to target it without changing directory.
Confirm it worked — git -C your-repo rev-parse --is-inside-work-tree prints true.
5. The .git directory was deleted or renamed
Reproduce it
cd my-app
mv .git .git_backup
git status
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — An over-eager cleanup, an rm -rf with a glob that matched dotfiles, an archive extracted without hidden files, or a file-sync tool that skips dot-directories all remove the only thing that makes the folder a repository. The working files survive; the history does not.
The fix
mv .git_backup .git # if it was renamed and still present
git status
# if it is genuinely gone and you have a remote:
cd ..
git clone https://github.com/your-org/my-app.git my-app-restored
What changed: .git is back under its expected name, or the history is recovered from the remote copy.
Confirm it worked — git log --oneline -1 shows your last commit and git status lists your real branch name.
6. The repository is on a filesystem Git refuses to trust (dubious ownership)
Reproduce it
sudo git clone https://github.com/your-org/app.git /srv/app
cd /srv/app
git status
# fatal: detected dubious ownership in repository at '/srv/app'
Why it happens — When the repository is owned by a different user than the one running Git, Git stops before using it. The headline message is the dubious ownership warning, but scripts that then run another Git command in a changed context — or older wrappers that swallow the first error — surface it as the generic not a git repository failure instead.
The fix
sudo chown -R "$USER":"$USER" /srv/app # preferred: make the owner match
# or, if the ownership is intentional and you accept the risk:
git config --global --add safe.directory /srv/app
What changed: the running user now owns the repository, or that exact path is explicitly marked safe.
Confirm it worked — git -C /srv/app status runs without any fatal message, and git config --global --get-all safe.directory lists the path if you took the second route.
More causes (4 remaining)
7. GIT_DIR points at a path that does not exist
Reproduce it
export GIT_DIR=/tmp/wrong/.git
cd ~/projects/my-app
git status
# fatal: not a git repository: '/tmp/wrong/.git'
Why it happens — GIT_DIR overrides the upward search entirely. Once it is set, Git uses that path and nothing else — so a stale value exported by a shell profile, a CI job, or a previous source makes every command fail no matter which directory you are standing in.
The fix
unset GIT_DIR
unset GIT_WORK_TREE # same trap, for the working tree side
git status
What changed: the override is gone, so Git resumes its normal search from the current directory.
Confirm it worked — env | grep -i '^GIT_' returns nothing relevant, and git rev-parse --git-dir prints .git.
8. A submodule or worktree .git file points at a missing location
Reproduce it
cd my-app/vendor/lib # a submodule
cat .git
# gitdir: ../../.git/modules/vendor/lib
rm -rf ../../.git/modules/vendor/lib
git status
# fatal: not a git repository: ../../.git/modules/vendor/lib
Why it happens — In submodules and linked worktrees, .git is a plain file holding a gitdir: pointer rather than a directory. If the target it points to was moved, pruned, or never created, Git follows a dangling pointer and reports no repository.
The fix
cd ../..
git submodule update --init --recursive vendor/lib
cd vendor/lib && git status
# for a stale linked worktree instead:
git worktree repair
What changed: the submodule's real git directory was recreated under .git/modules/, so the gitdir: pointer resolves again.
Confirm it worked — cd vendor/lib && git rev-parse --git-dir prints a path that exists on disk (ls it to confirm).
9. The command runs from a different directory than you think (scripts, hooks, CI)
Reproduce it
# package.json
{ "scripts": { "tag": "cd /tmp && git tag v1.0.0" } }
npm run tag
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — The working directory at the moment Git executes is what matters, not where you typed the command. A cd earlier in the script, a CI step with a custom working-directory, a Makefile recipe in a subshell, or a hook invoked outside the checkout all shift it.
The fix
{ "scripts": { "tag": "git -C \"$PWD\" tag v1.0.0" } }
# or make the script independent of the caller's location
REPO_ROOT="$(cd "$(dirname "$0")" && git rev-parse --show-toplevel)"
git -C "$REPO_ROOT" tag v1.0.0
What changed: the Git command names its repository explicitly with -C instead of inheriting whatever directory the script happened to be in.
Confirm it worked — Add pwd && git rev-parse --show-toplevel as the step immediately before the failing command and read the logged paths.
10. A Docker build or container never received the .git directory
Reproduce it
# .dockerignore
.git
FROM alpine
RUN apk add --no-cache git
COPY . /app
WORKDIR /app
RUN git rev-parse --short HEAD
# fatal: not a git repository (or any of the parent directories): .git
Why it happens — .dockerignore excludes .git from the build context, so COPY . /app copies the source files without it. Mounted volumes cause the same thing when you mount a subdirectory of the repo rather than the root.
The fix
FROM alpine
RUN apk add --no-cache git
ARG GIT_SHA
COPY . /app
WORKDIR /app
RUN echo "$GIT_SHA" > /app/REVISION
docker build --build-arg GIT_SHA="$(git rev-parse --short HEAD)" .
What changed: the commit SHA is computed on the host, where the repository exists, and passed in as a build argument instead of asking Git inside the image. Keeping .git out of the image also keeps it smaller.
Confirm it worked — docker run --rm your-image cat /app/REVISION prints the SHA, and the build no longer runs git inside the container.
## None of Those? Narrow It Down
- Run
pwd. If the path is not the repository you expected, the cause is location —cdto the right place and stop here. - Run
ls -aand look for.git. A.gitdirectory means a normal repo; a.gitfile means a submodule or linked worktree (go to step 5); nothing at all means the folder is not a repo (rungit initor re-clone). - Run
git rev-parse --show-toplevel. A path proves Git can resolve a repository from here, so your original command failed for an unrelated reason. The fatal message again means the search truly fails. - Run
env | grep -i '^GIT_'. AnyGIT_DIRorGIT_WORK_TREEvalue overrides the search and must be correct or unset — this rules the environment in or out. - If
.gitis a file, runcat .gitand thenlsthe path aftergitdir:. A missing target means a broken submodule or worktree link; fix it withgit submodule update --initorgit worktree repair. - Run
ls -ld . .gitand compare the owner toid -un. A mismatch points at dubious ownership rather than a missing repository. - If the command runs inside a script, hook, CI job, or container, print
pwdon the line before it. This separates "the repo is missing" from "the process is somewhere else". - Still failing: run
GIT_TRACE=1 git status 2>&1 | head -40. The trace shows the directories Git probed and the final git-dir it settled on, which tells you where the search stopped.
Why This Error Exists At All
Git has no concept of a server session, a project registry, or a "current project" setting. A repository is nothing more than a .git directory sitting in a folder, and the only way the git binary figures out which repository you mean is by looking at your current working directory and walking up the parent chain until it finds one. That walk is the entire discovery mechanism. When it reaches the filesystem root with nothing found, there is no fallback to try — hence fatal, not a warning.
The design is deliberate. Because discovery is purely positional, a repository is self-contained and relocatable: move the folder, rename it, copy it to another machine, and it still works, because nothing outside the folder records its existence. The cost of that property is exactly this error. Git cannot distinguish "you meant the repo two directories down" from "you meant no repo at all", so it refuses to guess rather than operating on something you did not intend. The search is also strictly upward, which is why standing beside a repository feels like a bug but is not one — descending into subdirectories could match several repositories at once, with no principled way to choose.
Once you see it as a resolution failure, the whole family becomes recognisable. GIT_DIR short-circuits the walk, so a stale value breaks every command regardless of location. A submodule's .git file is an indirection in the middle of the walk, so a dangling gitdir: pointer fails the same way. The dubious-ownership refusal is the walk finding a repository and then declining to use it. Containers, CI steps, and npm scripts fail because the process's working directory — not yours — is what seeds the walk. In every case, the question to ask is the same one Git asks: starting from where this process actually is, is there a readable .git above it?
Stop It From Coming Back
- Put the repository and branch in your shell prompt —
git-prompt.shwith__git_ps1for bash/zsh, or Oh My Zsh'sgitplugin, orstarshipwith its git modules. You see immediately when you are outside a repo. - In every script, resolve the root once with
REPO_ROOT="$(git rev-parse --show-toplevel)"and pass it to Git asgit -C "$REPO_ROOT" ..., so the script never depends on the caller's working directory. - Start scripts with
set -euo pipefailso a failedcdor a fatal Git call aborts the run instead of letting later commands execute in the wrong directory. - In CI, make the repo-dependent steps explicit: set
working-directoryon the step (GitHub Actions) and keep the checkout action'sfetch-depthhigh enough for the commands you run, rather than assuming the runner's default directory. - Compute Git metadata outside containers and inject it —
docker build --build-arg GIT_SHA="$(git rev-parse --short HEAD)"— and keep.gitin.dockerignoreso no image-time command can depend on it. - Gate repo-only tooling behind a check:
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || { echo "not in a git repo"; exit 1; }gives a readable message instead of a bare fatal.
Related Guides
-
TS2345: Argument of type is not assignable to parameter of type
-
TypeError: Cannot read properties of undefined (reading 'map')
Errors You Will Probably Hit Next
fatal: detected dubious ownership in repository at '/path/to/repo'fatal: your current branch 'main' does not have any commits yetfatal: 'origin' does not appear to be a git repository
This error is a question about location, not about Git's health. Answer pwd, then ls -a for .git, then env | grep GIT_ — in that order — and you will have identified the cause in under thirty seconds. When it happens inside a script, hook, or container, the directory to inspect is the process's, not yours, and printing pwd on the preceding line is faster than any amount of reasoning about it.
댓글
댓글 쓰기