Table of Contents

Introduction

If you've run git clean -f and found a stray tmp/ folder still sitting there afterwards, that's by design. Plain -f removes untracked files, but when a whole directory is untracked, it leaves that directory and everything in it alone. Adding -d tells it to, which is why git clean -fd is the form most people end up typing.

In this article, we'll:

  1. Watch git clean -fd delete an untracked file and an untracked folder
  2. Confirm that tracked changes survive it
  3. Cover how -d relates to -x, and the cases it still skips

What is git clean -fd?

git clean -fd is git clean -f plus -d, which lets it recurse into untracked directories and delete them along with everything inside. An untracked directory is one that holds no tracked files, which git status shows as a single line ending in a slash, like ?? tmp/. Empty directories count too (Git doesn't track directories, so an empty one is invisible to git status, but -fd removes it).

Like every form of clean, it skips files matched by .gitignore unless you add -x, and the deletion is permanent.

Watch it happen

Our sample repo has an untracked file and an untracked folder, on main at 8c02d5b, "Update dependencies". README.md is staged and app.py modified, next to an untracked notes.txt and an untracked tmp/ folder. Here's git clean -fd:

  1. Before: notes.txt and tmp/ are both untracked.
  2. Git deletes both from disk. tmp/ goes as one entry, contents and all.
  3. No commit or branch changes, and the tracked files aren't touched, since clean never considers them.

With plain git clean -f, only notes.txt would have moved. tmp/ would have stayed exactly where it was.

Before and after

git status --short before:

M  README.md
 M app.py
?? notes.txt
?? tmp/

and after:

M  README.md
 M app.py

The staged README.md and modified app.py are still there. notes.txt and tmp/ are gone. Git names each path as it removes it, with a trailing slash for the directory:

The raw git output, if you want to read along in text

git log --oneline --graph --all

before

* 1117a34 (feature) Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * 8c02d5b (HEAD -> main) Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

after

* 1117a34 (feature) Add search tests
* e5869f0 Fix typo in search box
* fc19889 Add search box
| * 8c02d5b (HEAD -> main) Update dependencies
| * a0b2db3 Add user settings page
|/  
* 96c4fc2 Fix header layout
* ae65976 Add login page
* 3e1ffe4 Add project skeleton
* 4114b2c Initial commit

what git printed

Removing notes.txt
Removing tmp/

-d, -x and -X

These flags stack, and it helps to be precise about what each one adds:

  • git clean -f removes untracked files, but not untracked directories.
  • git clean -fd adds untracked directories.
  • git clean -fdx also removes ignored files and directories: build output, node_modules/, virtual environments, and .env files with your local secrets in them.
  • git clean -fdX removes only ignored files and directories, and keeps your other untracked work.

The jump from -fd to -fdx is the big one. Everything .gitignore hides is usually either regenerated by a build or something you'd hate to lose, and -x doesn't distinguish between the two. Preview with git clean -n first: git clean -nd or git clean -ndx lists exactly what the real run would remove.

The first time I ran git clean -fdx in a Python project, it took my .venv along with the build folders, since the virtual environment is ignored. Nothing irreplaceable, just a reinstall, but it's the reason I now reach for -X when all I want is a fresh build, and check the list with -n first.

What -d still skips

  • Ignored files, without -x or -X. A folder that holds only ignored files survives -fd, because its contents do.
  • Nested repositories. An untracked directory with its own .git is left alone. Git needs -f twice (git clean -ffd) before it will delete another repository.
  • Tracked files, always. Use git restore or git reset --hard for those.

One more detail from the git clean documentation: when you give clean a pathspec, such as git clean -f tmp/, it removes the untracked files matching that path whether or not you pass -d.

Is it safe?

Destructive git-sim pre-flight

Permanently deletes 2 untracked path(s).

What you would lose

  • notes.txt (untracked — NOT recoverable)
  • tmp/ (untracked — NOT recoverable)
  • Untracked files are not in git's object store; deletion is permanent.

git-sim rates this destructive because neither notes.txt nor anything in tmp/ was ever in Git's object store. A directory can hold a lot of work, so the one-line tmp/ entry deserves a closer look than a single file would.

How to undo it

You can't, in Git. Untracked files were never committed or staged, so no Git command has a copy. Your editor's local history, your operating system's backups, or the build that generated the files are the only ways back. If you want untracked work out of the way but not gone, git stash -u sets it aside instead.

Try it on your repository

pip install git-sim
git-sim clean -fd

git-sim lists each file and directory the command would remove, marks them not recoverable, and deletes nothing.

Common questions

What is the difference between git clean -f and git clean -fd?

-f deletes untracked files but leaves untracked directories alone. -fd deletes untracked directories too, with everything inside them.

Does git clean -fd delete ignored files?

No. Ignored files survive unless you add -x (ignored and untracked) or -X (ignored only).

Why didn't git clean -fd remove a folder?

It might contain ignored files, which keep the folder alive, or it might be a nested Git repository, which needs git clean -ffd. git clean -ndx shows what adding -x would change.

How do I remove untracked files but keep a specific folder?

Exclude it with -e, for example git clean -fd -e config/, or add it to .gitignore.

Summary

In this article, we watched git clean -fd remove both notes.txt and the tmp/ folder while the tracked changes stayed, and covered how -d differs from -x and -X and what it still leaves alone.

Next steps

Preview first with git clean -n, every time. git clean covers the basics of what untracked means and why the deletion is permanent.