TL;DR: to remove untracked files in git, run git clean -fd — but always preview with git clean -nd first, because clean deletes permanently and they never hit the trash. Use -f for files, -fd for files and directories, and -fdx if gitignored build output should go too. The single most common complaint — “git clean is not removing my untracked files” — almost always means the files live inside an untracked directory (add -d) or they are ignored files (add -x). Untracked clutter is the normal byproduct of experiments, builds and clone-adjacent scripts; this guide shows how to preview every deletion, remove files from one directory only, and which combinations to never run in a repo you care about.
What does “untracked files” mean in git status?
Untracked means git sees the file on disk but has never been told to track it — it is not in the index and has no commit history. git status groups everything into three buckets:
$ git status --short
M src/app.ts # modified: tracked, changed
?? notes.txt # untracked: new file git doesn't know
?? build/ # untracked directory: entirely new to git That distinction matters because each bucket needs a different removal tool. Tracked-but-changed files are reverted with git restore or committed away — git clean will not touch them. Only the ?? lines are git clean territory. Ignored files (anything matched by .gitignore) are a hidden fourth bucket: they do not even show as ??, and git clean skips them unless you explicitly opt in with -x.
If your real problem is a tracked file that should never have been committed, cleaning is the wrong tool — that is a git rm --cached job, or a reset of the last commit as covered in git undo last commit: keep the changes.
How do I remove untracked files in git?
The core command is git clean -f. Without -f git refuses to delete anything and just prints a warning — a deliberate safety rail. The full routine looks like this:
# 1. See exactly what will be deleted (dry run — deletes nothing)
git clean -nd
# Would remove:
# notes.txt
# build/
# scratch/
# 2. Confirm nothing precious is in the list, then delete for real
git clean -fd Flag by flag:
-f/--force— required. Actually deletes untracked files.-d— recurse into untracked directories. Plain-fonly removes untracked files at the top level and reports the directories it refused to touch.-n/--dry-run— show what would be removed. Always run this first.-x— also delete ignored files (node_modules, build output,.env).-X— delete only ignored files, keeping untracked-but-not-ignored ones.-i— interactive mode; useful when the dry-run list is long.
A habit worth copying: treat git clean -nd like git diff — you look before you commit, you look before you clean.
Why is git clean not removing my untracked files?
Three real causes, in order of how often they bite:
1. The files are inside an untracked directory. With only -f, git removes loose untracked files but stops at directories, even reporting Would remove build/ without deleting it in a real run. Add -d:
git clean -fd 2. The files are gitignored. node_modules/, dist/, .venv/ — ignored paths are invisible to a plain clean. The dry run will not list them, and neither will the clean remove them. Opt in explicitly:
git clean -fdx # untracked + ignored files and directories 3. A nested git repository or submodule is in the way. Git never deletes another repo’s contents from the outside. Remove the submodule properly or pass --force twice (git clean -ffd), and prefer the first option.
If the dry run lists nothing but git status still shows ?? entries, you are probably in the wrong working tree — run git rev-parse --show-toplevel and check you are inside the repo you meant to clean.
How do I remove untracked files from a specific directory only?
Scope the clean by passing a path — everything else is left alone:
git clean -fd build/ # only inside build/
git clean -fd src/generated # one specific tree This is the answer to “I want to remove untracked files and folders in build/ but keep my scratch notes in the repo root”. The path is relative to your current directory, so running from the repo root scopes to the whole repo; running from a subdirectory scopes to that subtree.
How do I remove untracked files without deleting them?
When the dry run shows files you might want later, do not gamble — preserve first, clean second:
# Stash untracked files (including ignored ones with -a) without deleting
git stash push --include-untracked
git clean -fd # tree is clean
git stash pop # bring them back when needed git stash -u moves untracked files out of the tree but keeps them recoverable — that is the “remove without deleting” semantics people are actually looking for. For a preview you can keep, git clean -nd > clean-plan.txt gives you the exact list before committing to anything. There is no undo after git clean -f: deleted means gone.
git clean vs git rm vs git restore: which one when?
| Command | Touches | Removes from disk | Use when |
|---|---|---|---|
git clean -fd | Untracked files/dirs | Yes | Delete files git has never tracked |
git clean -fdx | Untracked + ignored | Yes | Full reset including node_modules, build output |
git rm <file> | Tracked files | Yes (staged) | Delete a file and record the deletion in git |
git rm --cached <file> | Tracked files | No | Stop tracking a file, keep it on disk |
git restore <file> | Tracked files | No | Discard local edits, keep the file |
The one-line rule: clean manages what git doesn’t know about; rm and restore manage what it does. Mixing them up is how people lose work — running git clean -fdx while believing it behaves like git restore.
What should you never run git clean on?
Two habits to avoid outright:
- Never run
git clean -fdxblindly in a monorepo or workspace. It deletes every ignored directory — that is everynode_modules, every virtualenv, every local.envin the tree. Regaining them can mean an hour of reinstallation, and a deleted.envmay not be recoverable at all. - Never alias clean with force baked in.
git config alias.wipe "clean -fd"feels efficient until you typo the path. Keep the dry run one keystroke away (git clean -nd) and make it a two-command ritual, preview then delete.
Also worth knowing: cleaning untracked files right before a [sync a fork with upstream] pull keeps the merge surface small — a tidy tree is the cheapest conflict insurance there is: sync a fork with upstream, step by step.
The safe git clean workflow, condensed
git status --short # what's in the tree?
git clean -nd # preview: what WOULD go?
git clean -fd # delete untracked files + directories
git clean -fdX # (optional) clear only ignored build output
git status --short # confirm: working tree clean Preview, delete, verify — thirty seconds, zero regret, and git status finally reads clean again.
— mrsaynothing