Bug: Claude Code silently deletes user files from ~/.claude/sessions/ — DATA LOSS
Description
Claude Code is silently and permanently deleting user files from ~/.claude/sessions/. Any file whose name begins with digits — most commonly date-prefixed files like 2026-03-14_session_notes.md — is mistakenly identified as a stale PID file and deleted without warning, confirmation, or any log entry.
This is not a hypothetical concern. We lost real work files repeatedly before tracking down the cause through hours of forensic investigation with inotify watchers and binary source extraction.
Impact
- Silent data loss — no warning, no confirmation prompt, no error message, no log. Files simply vanish.
- Affects every session — cleanup runs on every session start, every autocompact, and periodically via the tips system. There is no way to opt out.
- Unrecoverable — deleted files are not moved to trash or backed up. They are
unlink'd directly. - Affects all native Linux and macOS users — only WSL is excluded.
- Cross-session collateral damage — multiple conversation sessions share one OS process, so one session's autocompact can destroy another session's files.
~/.claude/sessions/is a reasonable user-writable location — there's nothing warning users that arbitrary files placed here will be destroyed.
Root Cause
The concurrent session cleanup function (minified as Qm$ in v2.1.75; searchable in source by the pattern parseInt(L.replace(/\.json$/,""), 10)):
async function concurrentSessionCleanup() {
let dir = getSessionsDir() // ~/.claude/sessions/
let files = await fs.readdir(dir)
for (let file of files) {
let pid = parseInt(file.replace(/\.json$/, ""), 10);
if (isNaN(pid)) continue; // skip files with no leading digits
if (pid === process.pid) continue; // skip our own PID file
if (isPidAlive(pid)) count++; // alive → count
else if (platform() !== "wsl") // dead → DELETE (except on WSL)
fs.unlink(join(dir, file)) // ← DELETES USER FILES
}
}
The bug: The function was designed to clean up <pid>.json files but iterates over ALL files in the directory. .replace(/\.json$/,"") is a no-op for non-.json files. JavaScript's parseInt("2026-03-14_notes.md", 10) returns 2026 — it parses leading digits and stops at the first non-digit character. Since PID 2026 isn't running, the file is deleted as "stale."
There is no guard to skip non-.json files. A single missing line of code causes permanent data loss.
When it triggers
The cleanup fires frequently and silently:
- Every session start — after registering
<pid>.json, the function runs to count concurrent sessions for telemetry. - Every autocompact — re-initialization triggers cleanup again. Since multiple conversation sessions share one OS process, one session's autocompact deletes files relevant to all sessions.
- Tips system — the "multiple sessions" tip calls the cleanup to check session count. This can fire unpredictably during normal use.
Deletions occur at turn boundaries — between turn_duration and the next message dequeue — making them nearly impossible for users to notice in real time.
Reproduction
- Start a Claude Code session
echo "my important notes" > ~/.claude/sessions/2026-01-01_notes.md- Start a second Claude Code session (or wait for autocompact / tip check)
- File is gone. No trace it ever existed.
Confirmed experimentally (5/5 predictions correct)
| File | parseInt result | Outcome |
|------|------------------|---------|
| 2029625_alive_pid.md | 2029625 (our PID, alive) | Survived ✓ |
| 99999_dead_pid.md | 99999 (dead PID) | Deleted ✓ |
| 2026-03-14_date_prefix.md | 2026 (dead PID) | Deleted ✓ |
| test_2026_no_leading_digit.md | NaN | Survived ✓ |
| 7_low_pid.md | 7 (dead PID) | Deleted ✓ |
All deletions confirmed via inotify IN_DELETE events with timestamps. Deletions are not immediate — they fire when session management code runs (26 seconds after trigger in our test).
Suggested fix
One line:
if (!/^\d+\.json$/.test(file)) continue;
This ensures only actual <pid>.json files are candidates for cleanup. Everything else is left alone.
Environment
- Claude Code v2.1.75
- Linux x86_64 (NFS-mounted home directory)
- Bun standalone binary
- Not affected: WSL (explicitly guarded with
platform() !== "wsl"check) - Affected: Native Linux, macOS
This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗