Contents

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 -f in the test did not appear there.
  • git reflog. It records where branches and HEAD have been, not files. The Git recovery slides cover everything it can rescue, which is committed work.
  • git restore or git 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 remove refused a worktree holding an untracked folder, --force removed it with exit code 0, and a worktree holding only an ignored .env went 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.

Aaron Belchamber

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.

Book a free 30-minute consult

Researched and drafted with AI, then reviewed and edited by the site's editors.