FAQ
Safety
Section titled “Safety”Is it safe to run?
Section titled “Is it safe to run?”Scanning is read-only: lu-cleaner, scan, doctor and the pickers never delete anything on their own. Nothing is preselected, and deletion needs an explicit choice from you: either a selection you made and confirmed in the picker, or clean --yes with a narrowing filter. Several rules add margin on top of that:
- Every item has a risk level. caution items (sessions, models, dirty worktrees…) are never picked by smart select, are skipped when you press
spaceon a whole category, need a typedyesin the picker, and are left out ofclean --yesunless you pass--risk caution, like every item that carries a warning. - Right before removal, every path goes through a safety guard. It refuses anything outside your home folder and per-user temp folder, system and top-level home folders, credentials and tool configurations, git repositories, and your project roots themselves.
- lu-cleaner also re-checks the state of each item at that moment: the file is still the one it scanned, the owning app is closed, no process works inside it, no database is open.
Start with a dry run to see exactly what would happen:
lu-cleaner clean --yes --smart --dry-runCan I undo a cleanup?
Section titled “Can I undo a cleanup?”No. The default method deletes permanently, like rm -rf, because that is the only way to free space immediately. Keep in mind what "deleted" means for each risk level:
- safe and moderate items are regenerated by their tool: a cache refills,
pod installorpnpm installrebuilds, an updater downloads a version again. - Removing a worktree keeps its branch (see the next question).
- If you want a safety net, use Trash mode. The space is then only freed when you empty the Trash.
Will I lose my branches when a worktree is removed?
Section titled “Will I lose my branches when a worktree is removed?”No. lu-cleaner removes worktrees with git worktree remove, which deletes the checkout and keeps the branch and its commits in the main repository. Commits you have not pushed are still on that branch.
lu-cleaner refuses to remove a worktree, unless you pass --force, when:
- it has uncommitted changes, including untracked files (whatever your git configuration hides);
- it is on a detached HEAD with commits that no branch, tag or remote contains (they would become unreachable);
- it is locked (
git worktree lock); - it contains another worktree or repository, which git would delete along;
- lu-cleaner cannot read its git status.
--dry-run runs the same checks. Git itself refuses dirty or locked worktrees as a second line of defence. Git-ignored files such as node_modules, Pods or .env do not block removal. When a worktree holds ignored secret or personal files that exist nowhere else (.env*, .npmrc, keystores, signing keys…), lu-cleaner marks it caution with a warning, because those are lost with the checkout.
A worktree git no longer tracks (its metadata or main repository verifiably gone) is an orphan: it is deleted as a plain folder, and only with --force. A worktree whose repository cannot be read is only reported. See Clean up AI agent worktrees.
Can it delete my source code?
Section titled “Can it delete my source code?”The guard refuses to delete any directory that is a git repository (one holding a .git folder, file or symlink, or a bare repository), your project roots, and any folder that contains them. Project artifacts are only proposed when their marker file sits next to them (for example package.json next to node_modules), and lu-cleaner checks that marker again right before deletion. A folder named build that is tracked source code is not proposed. See React Native projects.
What does --force bypass?
Section titled “What does --force bypass?”--force skips three kinds of checks: the "app is running" guards, the "a process is working inside this folder" check, and the worktree checks (uncommitted changes, commits on no branch, lock, nested repositories). It also accepts the items that require it, such as orphaned worktree folders. It does not bypass the safety guard, the open-database check, the check that the path is unchanged since the scan, the marker-file check, or the rule that clean --yes needs --risk caution for caution and warned items. Use it rarely, and after a --dry-run.
Understanding the results
Section titled “Understanding the results”Why was an item skipped?
Section titled “Why was an item skipped?”Right before cleaning, every item is checked again. If something changed or looks unsafe, lu-cleaner skips the item, cleans the others, and lists the reason under Skipped (or skipped: … in the picker):
| Message | Why | What to do |
|---|---|---|
Xcode is running — quit it first (or use --force) |
The item belongs to an app that is open. | Quit the app and run again. |
cannot verify that … is closed |
The process list, or lsof, could not be read. lu-cleaner fails closed. |
Run again. |
… is open by process 1234 — quit the app first |
A SQLite database (or its -wal/-shm file) is open. --force does not change this. |
Quit the app that uses it. |
in use: process 1234 is working inside … |
A shell, dev server or agent has that folder as its working directory. | Stop it, or cd elsewhere. |
in use: process 1234 runs a program or library from … |
Something is executing from that folder, such as a node version or an SDK tool. | Stop the process. |
current directory is inside … |
You ran lu-cleaner from inside the folder. | cd ~ and run again. |
… changed since the scan (different inode) — rescan |
The path was replaced since the scan (new clone, symlink…). | Scan again. |
marker [package.json] not found next to … anymore |
The artifact's marker file disappeared. | Scan again. |
blocked by safety guard: … |
The path is protected, outside the allowed areas, a git repository, or a project root. | Nothing: this is intended. Check your protect list if it surprises you. |
… contains the mount point … — eject it first |
A mounted volume or disk image lives inside the folder. | Eject it. |
parent directory … is read-only … |
The folder cannot be removed from its parent; nothing was touched. | Fix the permissions, or remove it yourself. |
simulator … is booted now — shut it down first |
The simulator was started after the scan. | xcrun simctl shutdown all. |
… exists again / a live Claude Code session runs in … |
Data proposed as orphaned belongs to a folder that is back or in use. | Scan again. |
N uncommitted change(s) — commit them or use --force |
Worktree with local changes. | Commit, stash or discard them. |
contains the git repository … (git would delete it along) / contains another worktree, … |
A repository or worktree lives inside the worktree. | Move or remove it first. |
needs --force: orphaned … |
An orphaned worktree folder: git can no longer see its uncommitted work. | Check it, then clean it with --force, or move it to the Trash. |
main repository … no longer exists … |
The repository was deleted or moved since the scan. | Rescan: the worktree is then listed as orphaned. |
detached HEAD with N commit(s) on no branch — they would be lost… |
Worktree commits would become unreachable. | git branch <name> inside the worktree. |
worktree is locked (git worktree lock) — use --force |
The worktree is locked. | git worktree unlock <path> if you are sure. |
git refused to remove it (…) |
Git's own checks failed. | Read git's message. |
already gone |
The path no longer exists. | Nothing. |
not possible in Trash mode (it would delete permanently) / already in the Trash |
Trash mode only moves files and folders. | Clean without Trash mode if you want them removed. |
not cleanable (…) |
The item is report only. | Nothing. |
cancelled |
You pressed ctrl+c. |
Run again. |
Why is nothing selected when I open lu-cleaner?
Section titled “Why is nothing selected when I open lu-cleaner?”Because deleting has to be your decision. lu-cleaner and lu-cleaner clean open with an empty selection: the footer says "nothing selected", the SELECTED column is blank, and d has nothing to clean until you choose. Earlier versions started with the recommended items already selected, so a single d and enter could remove far more than you had looked at. That no longer happens.
To choose:
spaceselects the item under the cursor. On a category, it selects everything in it except caution items, which you pick one by one.ais smart select: it selects the recommended items of the current view (every category on the overview, or the open category) and unselects the rest of that view. It is an explicit action, and you can look at what it picked before you clean.Aselects everything cleanable in the view,nnothing,iinverts.
Then press d. The confirmation dialog shows what will be removed and the total, and asks for a typed yes when a caution item is included. To rehearse, add --dry-run (-n).
If you want the recommended items selected from the start, ask for it: lu-cleaner clean --smart (or lu-cleaner --smart) opens the picker with them selected, and clean --yes --smart cleans them without a picker. The keyboard shortcuts list every key.
Why isn't an item recommended?
Section titled “Why isn't an item recommended?”Smart select (the ★ column, the a key, --smart) only picks what you would clean without thinking twice. An item is not recommended when:
- its risk is caution or never;
- it carries a warning: app running, uncommitted changes, unpushed commits, open in an editor, on an external volume…;
- its scanner vetoes it because deleting it is irreversible or its origin is uncertain: emptying the Trash, orphaned data that may still be wanted, a browser profile with saved logins… (see Scanner vetoes);
- it is still being measured;
- it is a build artifact of a project you worked on in the last 24 hours;
- it is a safe item smaller than 1 MB;
- it is a moderate item used more recently than
stale_after(14 days by default), unless its scanner knows it is dead weight (superseded versions, data of deleted folders).
Worktrees follow their own rule: they are recommended when clean and pushed and idle for 7 days, or merged into the default branch and idle for 1 day. To change the staleness threshold:
stale_after = "30d"You can always select a non-recommended item yourself.
Why do sizes differ from Finder or du?
Section titled “Why do sizes differ from Finder or du?”lu-cleaner reports the space allocated on disk (blocks), counts a hardlinked file only once, and, when data is shared with other folders through hardlinks or APFS clones, shows how much deleting would really free. Finder shows the logical size of files. For sparse files such as VM disk images, and for folders full of small files, the two can differ a lot. lu-cleaner's figures are close to du -sh.
I cleaned, but my disk is still full
Section titled “I cleaned, but my disk is still full”Run lu-cleaner doctor. The usual causes are APFS local snapshots, the Trash, files shared with a package store, and apps holding deleted files open. See Why freed space doesn't show up.
Configuration and history
Section titled “Configuration and history”How do I protect a folder?
Section titled “How do I protect a folder?”Add it to your configuration file. Open the file with lu-cleaner config edit, or create a commented sample with lu-cleaner config init.
# Never proposed or cleaned, nor any folder that contains them.protect = ["~/dev/shop-app/ios/Pods", "~/.ollama/models"]
# Never scanned and never cleaned (prefix match).exclude = ["~/dev/archive"]
# Skip whole categories.disabled_categories = ["containers"]protect adds to the built-in list (SSH keys, credentials, AI tool configs, keystores…). exclude also makes scans faster. Paths accept ~, and relative paths are resolved from your home folder. Check the result with lu-cleaner config show. See Configuration.
How do I see what was deleted?
Section titled “How do I see what was deleted?”lu-cleaner history # last 30 entries, newest firstlu-cleaner history --limit 0 # everythinglu-cleaner history --jsonEvery clean appends one line per item to ~/.local/state/lu-cleaner/history.jsonl (or under $XDG_STATE_HOME): time, kind, category, name, path or command (the location and first paths of a group item), method, status, size, the bytes really freed, and the error or message. Dry runs are not recorded. The history tells you what was removed, not how to get it back.
How do I clean only one kind of thing?
Section titled “How do I clean only one kind of thing?”Filter by category with -c or by item kind with -k, or use a dedicated command:
lu-cleaner clean -c simulators # picker, simulators onlylu-cleaner clean --yes -k node_modules --older-than 30d --dry-runlu-cleaner worktrees # only git worktreeslu-cleaner artifacts ~/dev/my-app # only this project's artifactslu-cleaner scan --json shows the kind of every item.
Why does clean --yes refuse to run?
Section titled “Why does clean --yes refuse to run?”clean --yes refuses to "clean everything". It needs a narrowing filter: --smart, --category/-c or --kind/-k. A -k naming a scanner that spans several categories (-k catalog) is not enough on its own, and a tool name is not a category (-c cursor is refused: use -c ai or a -k kind). It also leaves out caution items and items with a warning unless you pass --risk caution, and lists the latter under Held back. These rules exist so that a script, a cron job or a coding agent cannot wipe your sessions or dirty worktrees by accident. See Automation.
Trash, permissions and macOS
Section titled “Trash, permissions and macOS”What does Trash mode do?
Section titled “What does Trash mode do?”Instead of deleting, lu-cleaner moves each path to ~/.Trash, naming it name 2, name 3… if the name is already taken there (nothing already in the Trash is ever replaced). Turn it on with --trash, with use_trash = true in the configuration, or with the t key in the picker; --trash=false turns it off for one run.
- Space is not freed until you empty the Trash. Summaries and the history count moves to the Trash apart, never as freed, and remind you.
- Nothing is deleted permanently. Only files and folders are moved. Commands (such as
xcrun simctl delete) and worktree removals cannot be undone, so they are skipped, and so are items already in the Trash. - A path on another volume cannot be moved to your Trash, and that item fails.
Use it when you want to double-check, then empty the Trash in Finder, or with lu-cleaner's own "Trash" item in the system category (in delete mode).
Does it need Full Disk Access?
Section titled “Does it need Full Disk Access?”No, lu-cleaner works without it. macOS protects a few folders, though: without Full Disk Access, lu-cleaner cannot measure ~/.Trash or your iPhone/iPad backups, and analyze reports unreadable entries. In that case the Trash item asks Finder to empty the Trash instead.
It also never touches the data of other apps (~/Library/Containers/<app>, ~/Library/Group Containers/<group>) without Full Disk Access: on macOS 14 and later, the first access would open a permission prompt and block the scan. What lives there, such as Docker Desktop logs, is listed as report-only with the warning needs Full Disk Access, and the analyzer shows those folders locked.
To grant it, open System Settings › Privacy & Security › Full Disk Access, enable your terminal app (Terminal, iTerm2, Ghostty…), then restart the terminal.
Does it need sudo?
Section titled “Does it need sudo?”No, and do not run it with sudo. lu-cleaner works on your user account: it only deletes inside your home folder and your per-user temp folder, and refuses system locations. The only commands that would need administrator rights, such as deleting APFS local snapshots, are printed by doctor for you to run yourself.
Does it work on Linux or Windows?
Section titled “Does it work on Linux or Windows?”No. lu-cleaner is macOS only. It depends on APFS, macOS system calls, ~/Library, xcrun simctl, tmutil and the way macOS apps store their data. Release binaries are universal (Apple silicon and Intel).
What about an external SSD?
Section titled “What about an external SSD?”lu-cleaner never deletes anything outside your home folder and temp folder, so nothing on /Volumes/… is ever removed. Items whose real location is on another disk (an Android SDK, a model store or a project moved to an external SSD and symlinked back) are shown as report only, "on external volume — no internal gain". Worktrees whose main repository is on an unmounted volume get the status "unknown" and are left alone. To clean an external drive, use Finder or a disk analyzer.
Do I need to quit my apps first?
Section titled “Do I need to quit my apps first?”It helps. Items owned by an app (Xcode, Simulator, Cursor, Codex, the ChatGPT app, Claude desktop, Android emulators…) show a warning such as "Cursor is running — quit it before cleaning" while that app runs. Smart select never picks them, clean --yes holds them back, and they are skipped during the clean. Everything else is cleaned normally. lu-cleaner doctor lists the running apps that block cleaning.
Does lu-cleaner send data anywhere?
Section titled “Does lu-cleaner send data anywhere?”Optional technical error reports are disabled until explicit consent. They include fixed error codes and lu-cleaner source frames, without personal paths, file contents, raw logs or a permanent user/device identifier. diagnostics status, enable, disable and export let you control them. Without consent, lu-cleaner makes no diagnostic network requests. Local tools and cleanup commands can have their own network behavior.
Other tools
Section titled “Other tools”How is it different from mole, CleanMyMac or npkill?
Section titled “How is it different from mole, CleanMyMac or npkill?”- CleanMyMac and mole maintain the whole Mac: system junk, app uninstaller, analyzer. They cover some developer caches too.
- npkill finds
node_modules(or other folder names) in any project, on any OS. - lu-cleaner is specialized for macOS developer machines running React Native, iOS and Android projects with AI agents. It knows worktree status, simulator internals, Android SDK packages, package-manager stores and AI tools data, and re-checks every path before deleting it. It does not uninstall apps or clean system folders.
They work well together. See the comparison.