[BUG] Cleanup sweep calls rmdir on every directory under ~/.claude/projects, deleting foreign empty directories and ignoring cleanupPeriodDays

Status Open
Reported on v2.1.233
Maintainer reply None cached
Activity 2 comments · opened Aug 15, 2026

Summary

Claude Code calls rmdir on every directory in the whole tree under ~/.claude/projects/, regardless of age and regardless of cleanupPeriodDays. Directories that happen to be empty are removed, including directories that do not belong to Claude Code.

In our case this repeatedly destroyed a Syncthing folder marker (.stfolder, an empty directory by design), which stopped a backup sync four times in one day with folder marker missing (this indicates potential data loss).

This is not the known transcript cleanup. It is a separate, undocumented sweep, and it ignores the retention setting.

Environment

  • Claude Code 2.1.233, installed at ~/.local/share/claude/versions/2.1.233
  • Linux Mint 22.3 (Ubuntu noble base), kernel 6.8
  • cleanupPeriodDays: 365 in ~/.claude/settings.json

What we measured

We could not identify the cause with inotify, because the process snapshot always arrives after the deleting process has exited. So we installed a kernel audit rule:

-a always,exit -F arch=b64 -S rmdir   -F dir=/home/USER/.claude/projects -k markerkill
-a always,exit -F arch=b64 -S unlinkat -F dir=/home/USER/.claude/projects -k markerkill

The next occurrence was caught in full. One single burst, within a few milliseconds:

| | |
|---|---|
| rmdir calls in the burst | 461 |
| failed with ENOTEMPTY | 460 |
| succeeded | 1 — the empty .stfolder |

The calling process:

proctitle = claude --dangerously-skip-permissions
exe       = /home/USER/.local/share/claude/versions/2.1.233
comm      = "Bun Pool 0"
pid 3004802, ppid = the terminal emulator, uid = the normal user

The successful deletion:

type=SYSCALL ... syscall=rmdir success=yes exit=0 ... comm="Bun Pool 0"
              exe=/home/USER/.local/share/claude/versions/2.1.233 key=markerkill
type=PATH    ... name=/home/USER/.claude/projects/.stfolder
              nametype=DELETE mode=dir,775

A representative failure, same burst, same millisecond range:

type=SYSCALL ... syscall=rmdir success=no exit=ENOTEMPTY ... key=markerkill
type=PATH    ... name=/home/USER/.claude/projects/<a session directory> nametype=DELETE

Three things that look wrong

1. It targets directories it does not own. The sweep called rmdir on .git, .git/hooks, .git/objects and .git/refs of a git repository that lives in that folder, and on the Syncthing marker. Nothing in ~/.claude/projects/ is guaranteed to belong to Claude Code — users put version control, sync tooling and backups there.

2. It ignores cleanupPeriodDays. Ours is set to 365. The deleted directory was 2 hours 19 minutes old. The documented behaviour is that session files and orphaned worktrees are deleted at startup once they exceed the configured age. Removing arbitrary empty directories of any age is neither covered by that sentence nor, as far as we can find, documented anywhere.

3. Timing does not match "at startup". The burst came 1215 seconds (20 min 15 s) after that session started, while the session was idle with no user activity. Four other Claude Code sessions were running at the same time and produced zero rmdir calls in the same two-hour audit window, so it is not a per-session periodic task either.

Impact

~/.claude/projects/ holds session transcripts, and users back them up. Ours is a Syncthing folder for exactly that reason. Syncthing's marker is an empty directory, so this sweep removes it, Syncthing then refuses to sync and warns about potential data loss. Between the deletion and our detection the backup silently stood still; on the first occurrence, before we had monitoring, it stood still for over 11 hours.

Syncthing's own FAQ predicts this class of failure: "Some software removes empty folders, and the .stfolder is often empty. To remediate, recreate the .stfolder and add a dummy file in it, or add an exception to your cleaning software." Claude Code currently belongs in that category of software.

Expected behaviour

Any of these would fix it:

  • Restrict the sweep to directories Claude Code created itself, matching the known session/subagent/workflow layout, instead of walking the whole tree.
  • Honour cleanupPeriodDays for directory removal too, not only for files.
  • Skip dot-directories that are not Claude Code's own (.git, .stfolder, …).
  • At minimum, document that the sweep exists and that it removes arbitrary empty directories, so users know not to place sync markers or empty-by-design directories there.

Workaround for other users

Put any file inside the empty directory you need to keep. rmdir then fails with ENOTEMPTY like it does for the other 460. For Syncthing specifically this is the officially recommended remedy in their FAQ.

Reproduction

  1. mkdir ~/.claude/projects/.probe (leave it empty)
  2. Optionally arm an audit rule as above to see the calls
  3. Start a Claude Code session and leave it idle
  4. The empty directory disappears; directories with content survive

We can supply the full 2831-line audit record if useful.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗