Hooks, LFS & submodulesAlways fixable

Remove a Git submodule completely

How do I remove a Git submodule — and why does re-adding it say it already exists in the index?

If you’re seeing this error

fatal: '<path>' already exists in the index
fatal: could not lookup name for submodule '<path>'
error: pathspec '<path>' did not match any file(s) known to git

You’re in the right place — the fix is below.

Short answer

git submodule deinit -f <path>, then git rm -f <path>, then delete .git/modules/<path>. All three matter: deinit clears the config, git rm removes the gitlink and the .gitmodules entry, and the leftover .git/modules copy is what makes re-adding it fail with 'already exists in the index'.

quick fix
git submodule deinit -f -- libs/vendor
git rm -f libs/vendor
rm -rf .git/modules/libs/vendor
git commit -m "Remove libs/vendor submodule"

The complete three-step removal — skipping any one leaves debris

Does this match your situation?

  • 'fatal: '<path>' already exists in the index' when adding a submodule at a path you already deleted.
  • The submodule folder is gone but .gitmodules still lists it.
  • git status shows the submodule as modified or as a deleted directory that will not go away.
  • You want to convert a submodule into ordinary tracked files.

Step-by-step fix

  1. See what is actually registered

    A submodule exists in four places: .gitmodules, .git/config, the index as a special gitlink entry, and .git/modules as a full clone. Removing it means clearing all four, and this shows you which ones are still populated.

    step 1
    git submodule status
    cat .gitmodules
    git config --get-regexp '^submodule\.'
    ls .git/modules
    
  2. Deinit — unregister and empty the folder

    deinit removes the submodule's section from .git/config and empties its working directory, while leaving the gitlink in the index. This is also how you temporarily switch a submodule off without removing it from the project.

    step 2
    git submodule deinit -f -- libs/vendor
    

    -f is required if the submodule has local modifications. Check inside it first if you might have unpushed commits there.

  3. git rm — drop the gitlink and the .gitmodules entry

    git rm on a submodule path is special-cased: it removes the gitlink from the index and edits .gitmodules for you. Deleting the folder with rm -rf instead is what leaves the half-removed state most people end up in.

    step 3
    git rm -f libs/vendor
    git status                  # .gitmodules should show as modified
    
  4. Delete the internal clone — the step everyone misses

    Git keeps the submodule's real repository in .git/modules and never removes it automatically, as a safety measure against losing unpushed work. It is also exactly what makes git submodule add at the same path fail with 'already exists in the index' long after the folder is gone.

    step 4
    rm -rf .git/modules/libs/vendor
    
    Show the Windows / PowerShell version
    Windows / PowerShell
    Remove-Item -Recurse -Force .git\modules\libs\vendor
    
  5. Commit the removal

    The change to .gitmodules and the removed gitlink both need committing, or the next person to pull will get the submodule back.

    step 5
    git commit -m "Remove libs/vendor submodule"
    git push
    
  6. Or convert it into ordinary files instead

    If you want the code to stay but stop being a submodule, remove the link first and then copy the files back in as normal tracked content. The submodule's own history is not preserved this way — use git subtree if that matters.

    step 6
    cp -r libs/vendor /tmp/vendor-keep
    git submodule deinit -f -- libs/vendor
    git rm -f libs/vendor
    rm -rf .git/modules/libs/vendor
    cp -r /tmp/vendor-keep libs/vendor
    rm -rf libs/vendor/.git
    git add libs/vendor
    git commit -m "Vendor libs/vendor directly"
    

Why this works

A submodule is not a folder — it is a single entry in the index called a gitlink, holding one commit hash of a completely separate repository, plus a URL recorded in .gitmodules and a live checkout Git stores in .git/modules. Those four pieces are maintained independently, and no single command clears them all, which is why partial removals are so common. 'Already exists in the index' is the giveaway: git submodule add refuses because the internal clone under .git/modules is still there, so Git believes it is being asked to create something it already has, even though the working directory shows nothing at all.

If that didn’t work

  • Remove the config section by hand: git config --remove-section submodule.libs/vendor
  • Edit .gitmodules directly and delete the stanza, then git add .gitmodules.
  • If the index entry survives, force it out with git rm --cached libs/vendor.
  • Check for the same path in a second worktree, which keeps its own checkout.

How to stop it happening again

  • Use git rm rather than rm -rf on a submodule path — it edits .gitmodules for you.
  • Push any work inside the submodule before deinit; its commits live only in .git/modules.
  • Prefer git subtree when contributors keep forgetting --recurse-submodules.
  • Set submodule.recurse true so pull and checkout keep submodules in step automatically.

Commands used in this guide

git submodule

Embed another Git repository inside yours at a pinned commit.

git rm

Delete a file from the repository - or just stop tracking it.

git config

Read and write Git settings for one repo, your user, or the whole machine.

git subtree

Vendor another repository into a subdirectory - without submodules' friction.

git status

Show what's changed, what's staged, and what Git is ignoring.

Still stuck?

Search the full command reference and every other rescue guide — there are 76 of them, covering everything from detached HEAD to force-push disasters.

Browse all guides →

Frequently asked questions

Why does git submodule add say 'already exists in the index'?

Because a previous removal left the internal clone in .git/modules/<path>, or the gitlink is still in the index. Delete .git/modules/<path> and run git rm --cached <path>, then add it again — the working directory looking empty tells you nothing about either location.

What is the difference between git submodule deinit and git rm?

deinit unregisters the submodule and empties its folder but leaves the gitlink in the index, so the project still expects it. git rm removes the gitlink and the .gitmodules entry, which is what actually deletes it from the project. A complete removal needs both, plus deleting .git/modules/<path>.

Does removing a submodule delete its repository?

Not the upstream one — that is a separate repository at its own URL and is untouched. You do lose the local clone in .git/modules, so push any commits you made inside the submodule before you deinit it.

Why is my submodule always in detached HEAD?

That is normal and intended: the parent repository records one exact commit, so Git checks that commit out directly rather than a branch. If you want to work inside it, git checkout <branch> in the submodule first, or set submodule.<name>.update to merge or rebase in .gitmodules.

How do I replace a submodule with the plain files?

Copy the folder somewhere safe, deinit and git rm the submodule, delete .git/modules/<path>, copy the files back, delete the inner .git directory, then add and commit them as ordinary files. The submodule's history is not carried over — use git subtree if you need to keep it.

Related rescue guides

← All Git Rescue guides