10/29/2026
git clean Deleted Untracked Files: Recover and Prevent
Short answer: git clean -f deletes untracked files outright. They skip the Recycle Bin and the reflog never saw them, so there is no undo. What you can get back is whatever exists somewhere else: another worktree, a stash you made with -u (even a dropped one), a file you once staged, your editor's local history, or a backup. Prevent the rest by running the same command with -n first, naming the exact paths, and committing real work before anyone cleans.
Every command below ran on Git 2.54.0 for Windows, in a throwaway repository.
What happened on 09/08/2026
The branded email templates I had made for each of my five sites vanished from my site-building repository. They lived in one folder, email-templates/, and had never been committed. When I asked a Claude Code session for them so I could import them into my mail client, it found the folder gone, and every per-site templates folder under it. Git had no copy.
Minutes earlier, the same session had handed me a git clean to run. My permission rules stop agents from running destructive git commands themselves: its own attempt had been denied, so it gave me the command instead. That command named four specific files, and it was aimed at a different repository, through git -C. The session's first diagnosis was a git clean -f run without paths in the site-building repository, and it took the blame plainly: "My fault for handing you a git clean command at all." The retro filed that day calls the handed command "the likely origin."
That diagnosis is an inference, and the record does not support it as stated. The command that was handed over had paths, and no clean in the record reaches email-templates/. What removed the folder is not established. Only the recovery is.
All five sites' templates survived in exactly one place: an unrelated worktree another session happened to have open. They were copied back and committed, 20 files.
What git clean -f actually removes
The git clean documentation defines the scope: untracked files only, ignored files only with -x, untracked directories only with -d, and nested repositories only with a second -f. Here is what each form did in the test repository, which held an untracked file, an untracked email-templates/ folder, an ignored .env and node_modules/, and a nested repository:
| Command | Untracked files | Untracked directories | Ignored files | Nested repository |
|---|---|---|---|---|
git clean |
Refuses: "clean.requireForce is true and -f not given" | |||
git clean -f |
Removed | Kept | Kept | Kept |
git clean -fd |
Removed | Removed | Kept | Kept |
git clean -fdx |
Removed | Removed | Removed | Kept |
git clean -fdX |
Kept | Kept | Removed | Kept |
git clean -ffd |
Removed | Removed | Kept | Removed |
git clean -f -- untracked.txt |
Only that file | Kept | Kept | Kept |
git clean -f email-templates |
Everything untracked under that path, directories included |
Two things in that table catch people out. First, a path makes -d irrelevant: the documentation says so, and git clean -n email-templates reported Would remove email-templates/ without any -d. Naming a directory is not a narrower clean; it is permission to delete all of it. Second, -x is in most "reset my repo" recipes, and it takes your .env with it.
The table also shows why my own story has a loose end. On 2.54 -f alone left an untracked directory exactly where it was, and the one command the session handed me named four files, so it could not have taken a folder in another repository. A whole directory went, so whatever removed it reached directories: a -d, a path to the folder, or a different command. The session record does not contain it.
Where a copy might still exist
Look before you build, install or clean anything else. As Microsoft's recovery guide puts it, a deleted file's space is only marked free, and any use of the computer can overwrite it.
| Place | Holds a copy when | How to check |
|---|---|---|
| Another worktree | The files were created or copied there | Search every worktree path |
A stash made with -u |
You stashed untracked files at any point, even if later popped or dropped | git fsck --unreachable |
| Git's object store | The file was staged with git add at least once |
git fsck --lost-found |
| Editor local history | You saved the file in an editor that keeps one | The editor's history view |
| Backup or sync | A backup or sync tool with versions covers the folder | That tool's version history |
| Undelete tool | The disk space has not been reused yet | Windows File Recovery |
Another worktree
git worktree lists every working directory attached to the repository, each with its own untracked files, and one of them is where my templates turned up. Search them all for the folder you lost:
git worktree list --porcelain | sed -n 's/^worktree //p' | while IFS= read -r wt; do ls -d "$wt/email-templates" 2>/dev/null; done
C:/.../git-test/final-agent/email-templates/
Copy what you find back with cp -r, then commit it before anything else happens to that worktree.
A stash, even a dropped one
git stash with -u saves untracked files into the repository before cleaning them away, so a stash entry you popped or dropped can still hold them. The documentation's recovery incantation filters on --grep=WIP, which only matches stashes saved without a message; my test stash, saved as "before cleanup", did not show up with that filter. Drop it:
git fsck --unreachable | grep commit | cut -d\ -f3 | xargs git log --merges --no-walk --format='%h %s'
9cd3861 On main: before cleanup
In the test, the untracked files sat in that commit's third parent, labeled "untracked files on main". List them, then restore the folder:
git show --name-only --format='%s' 9cd3861^3
git restore --source=9cd3861^3 -- email-templates
untracked files on main: ac7e878 init
email-templates/site-a/welcome.html
A file you staged once
git add writes the file's contents into the object store. If you later unstaged it and it was cleaned, the contents are still there as a dangling blob. git fsck with --lost-found writes each one to a file:
git fsck --lost-found
grep -l "Site B" .git/lost-found/other/*
dangling blob ce227734cb7e7c3dca51c39ad77b42e646df8891
.git/lost-found/other/ce227734cb7e7c3dca51c39ad77b42e646df8891
You get contents, not filenames, so search for text you remember. A file that was never staged leaves nothing here: in a fresh repository, cleaning a never-added folder left git fsck --lost-found with nothing to write.
Outside git
Two tools outside git are worth a look: Visual Studio Code's local history and Microsoft's Windows File Recovery. VS Code keeps a copy each time you save a file in the editor, on by default, and its "Local History: Find Entry to Restore" action exists for deleted files. It only helps for files you saved in the editor, not ones a script or an agent wrote straight to disk.
Windows File Recovery is a command-line app from the Microsoft Store that tries to recover deleted files from that free space; Microsoft warns the space may already be overwritten, especially on a solid-state drive. I have not tested either tool against a git clean.
What will not bring them back
- The Recycle Bin. A file deleted by
git clean -fin the test did not appear there. git reflog. It records where branches andHEADhave been, not files. The Git recovery slides cover everything it can rescue, which is committed work.git restoreorgit checkout. Both need a commit, an index entry or a stash to restore from. An untracked file has none.
Always -n first, and name the paths
The dry run is the whole safety mechanism. Run it with the exact flags you intend, read every line, then change n to f and nothing else:
git clean -nd -- email-templates/old-drafts
git clean -fd -- email-templates/old-drafts
Would remove email-templates/old-drafts/
Removing email-templates/old-drafts/
git clean -i shows the same list and waits while you clean, filter, select or quit. Unsure about the list? Stash instead: git stash push -u -m "before cleanup" leaves the same clean tree and can be undone. Stashes are shared across worktrees, as the worktree guide shows, so another session can pop yours.
Leave clean.requireForce at its default of true; it is why a bare git clean refuses to run.
Commit real work before anyone cleans
To git, an untracked file is not saved anywhere. git status --short marks it ??, and a folder of deliverables showing ?? email-templates/ is one command from gone. Commit it, even as a work-in-progress commit on its own branch. If a file must stay out of the repository, .gitignore protects it from git clean -fd, but not from -x, so keep a copy outside the checkout too.
The Git cheat sheet keeps stash and clean side by side for this reason: one saves untracked work, the other erases it.
Making an agent's cleanup safe
Whichever agent you run, Claude Code, GitHub Copilot or Antigravity, blocking it from destructive commands is not the same as making them safe. My block did its job. The command came to me instead. It named its files and its repository, and it came without a dry run.
The retro filed that day drew two rules:
- A clean command handed to someone else names explicit paths and says what it will remove.
- An untracked directory of real deliverables is a finding to raise immediately. The next step is a commit, not a clean.
My own reading adds a third, because the command that day did name its paths and I still cannot say what removed the folder: ask for the -n output of the exact command, run in the directory it will run in, and compare the list with what you expect before you change n to f. Paths tell you what a command is aimed at. The dry run tells you what it reaches.
Two more follow from the same record:
- Check a worktree before removing it. In the test,
git worktree removerefused a worktree holding an untracked folder,--forceremoved it with exit code 0, and a worktree holding only an ignored.envwent with no warning at all. My templates' last copy was in a worktree, and cleaning that worktree up would have taken it. - Make the agent ask. Claude Code's permission rules accept command patterns such as
Bash(git clean *)in an ask or deny list, so the command stops for a person to read rather than running on its own.
Then read what it hands you the way you would read its code: as a claim to check, not a result to trust.

Let my Claude help your Claude
If you want a second pair of eyes on how your own setup uses AI-assisted development, that's what this is: me, personally, for AI specifically. Broader business growth and technology consulting is Brandager's own team.
Researched and drafted with AI, then reviewed and edited by the site's editors.