[BUG] cleanupPeriodDays: 99999 ignored — 490 sessions silently deleted despite explicit setting

Open 💬 18 comments Opened Mar 31, 2026 by TweedBeetle

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

cleanupPeriodDays: 99999 has been set in ~/.claude/settings.json since January 22, 2026 (verified via automated hourly git backups of dotfiles). The setting was never removed or modified — every backup commit from Jan 22 through today shows cleanupPeriodDays: 99999. Despite this, 490 session JSONL files were silently deleted.

Evidence

1. The setting was always present (git-verified):

2026-03-27 ce1a39c9 cleanup=99999
2026-03-25 f15a7447 cleanup=99999
... (every backup since Jan 22) ...
2026-01-22 2a2aece8 cleanup=99999  ← first set

Before Jan 22, cleanupPeriodDays was absent (default 30). It was added once and never changed.

2. sessions-index.json proves the sessions existed:

Scanning all sessions-index.json files across ~/.claude/projects/:

Total indexed sessions with existing JSONL: 0
Total indexed sessions with MISSING JSONL:  490

Missing sessions by month:
  2025-12: 44
  2026-01: 382
  2026-02: 64

Overall missing range: 2025-12-17 to 2026-02-04

Every single indexed session has had its JSONL file deleted. The index metadata (including fileMtime) survives but the files are gone.

3. sessions-index.json itself is stale:

The most recent sessions-index.json modification is January 31, 2026. CC stopped maintaining the index at some point (version update?). Current sessions (385 JSONL files from Feb 25+) are not in any index.

4. Current JSONL files start much later than expected:

Earliest JSONL:  2026-02-25 (1 file)
Next oldest:     2026-03-13 (bulk starts here)

90 days of expected data reduced to ~34 days of actual data.

5. The settings.json schema uses .passthrough():

Binary analysis of v2.1.87 confirms the settings Zod schema uses .passthrough(), so extra keys don't cause validation errors. All keys in the user's settings.json (cleanupPeriodDays, env, hooks, permissions, statusLine, enabledPlugins, extraKnownMarketplaces, alwaysThinkingEnabled, effortLevel, autoMemoryEnabled, skipDangerousModePermissionPrompt) are recognized in the current schema.

Possible Root Causes

  1. Version upgrade reset: A CC version upgrade between Feb-Mar 2026 may have introduced a code path where cleanup ran before settings were fully loaded, or where the settings reader returned null (triggering the ?? 30 default fallback in G$H()).
  1. Subagent context leak (related to #39667 comment): The user runs many autonomous spawned sessions with --dangerously-skip-permissions. If cleanup runs in a subprocess with incorrect HOME or failed settings resolution, cleanupPeriodDays falls back to default 30.
  1. Cleanup ran from a CC version that didn't recognize all schema keys: Though current v2.1.87 uses .passthrough(), an older version might have used .strict() mode, causing the entire settings object to fail validation.

What Should Happen

  • cleanupPeriodDays: 99999 should prevent ALL cleanup of session files
  • If settings fail to parse for any reason, cleanup should NOT fall back to the 30-day default — it should skip cleanup entirely (fail safe, not fail destructive)
  • The current fallback pattern ((settings || {}).cleanupPeriodDays ?? 30) * 86400000 is dangerous: a null settings object silently triggers aggressive cleanup

Suggested Fix

// Current (dangerous):
let q = ((X8()||{}).cleanupPeriodDays ?? xGK) * 24*60*60*1000;

// Safe alternative:
const settings = X8();
if (settings === null || settings === undefined) {
    log("Settings failed to load — skipping cleanup");
    return; // Don't clean up if we can't read the user's preference
}
let q = (settings.cleanupPeriodDays ?? xGK) * 24*60*60*1000;

Error Messages/Logs

No error messages. No warnings. Files silently disappeared. The only evidence is:

  • sessions-index.json metadata referencing files that no longer exist
  • Git history proving the setting was always 99999

Steps to Reproduce

  1. Set cleanupPeriodDays: 99999 in ~/.claude/settings.json
  2. Use Claude Code for 60+ days
  3. Observe that sessions older than ~30 days are deleted despite the setting

Related Issues

  • #39667 — Session .jsonl files silently deleted (same symptoms, different user)
  • #22547 — Data loss through bad default settings (same root cause family)
  • #23710 — cleanupPeriodDays: 0 silently disables persistence
  • #16970 — Losing chat history

Claude Model

Opus

Is this a regression?

Yes

Last Working Version

Unknown — the deletion happened silently between Feb-Mar 2026

Claude Code Version

2.1.87

Platform

Anthropic Console (claude.ai)

Operating System

macOS (Darwin 25.1.0)

Terminal/Shell

zsh

View original on GitHub ↗

18 Comments

github-actions[bot] · 3 months ago

Found 3 possible duplicate issues:

  1. https://github.com/anthropics/claude-code/issues/39667
  2. https://github.com/anthropics/claude-code/issues/22547
  3. https://github.com/anthropics/claude-code/issues/16978

This issue will be automatically closed as a duplicate in 3 days.

  • If your issue is a duplicate, please close it and 👍 the existing issue instead
  • To prevent auto-closure, add a comment or 👎 this comment

🤖 Generated with Claude Code

r-ef · 3 months ago

your settings file failed zod validation which caused sessions older than 30 days to be deleted, even if you have cleanupPeriodDays set

TweedBeetle · 3 months ago

Not a duplicate of the linked issues

The triage bot linked three issues, but none are the same bug:

| Issue | What it's about | Why it's different |
|-------|----------------|-------------------|
| #39667 | JSONL files deleted, sessions-index stale | Reporter corrected themselves: their files were legitimately cleaned by the 30-day default because they never set cleanupPeriodDays |
| #22547 | Bad default (30 days) causes unexpected data loss | About the default being too aggressive. Our issue: the explicit setting (99999) was ignored |
| #16978 | cleanupPeriodDays: 99999 ignored after v2.1.1 upgrade | This is the closest match, but it's closed as stale with no resolution |

This issue is about the setting being ignored, not about the default being bad. #39667 and #22547 are about users who didn't know about the setting. Our case: the setting was present, verified in git history across 60+ hourly backup commits, and still didn't prevent deletion.

Response to @r-ef's comment

your settings file failed zod validation which caused sessions older than 30 days to be deleted

Investigated this. The current CC binary (2.1.85+) actually has a guard for exactly this case:

"Skipping cleanup: settings have validation errors but cleanupPeriodDays was explicitly set."

So in current versions, validation errors + explicit cleanupPeriodDays = cleanup SKIPPED (protective). The schema also uses .passthrough() (confirmed in both 2.1.85 and 2.1.87), so extra keys don't cause validation errors anyway.

The most likely scenario: the deletion happened during an upgrade to an older version (possibly around v2.1.1 per #16978) that didn't have this guard yet. That version hit validation errors, fell through to the 30-day default, and purged the files. The guard was added later as a fix - but the data was already gone.

Unique evidence in this report

What this issue contributes that the linked issues don't:

  1. Git-verified settings timeline: Hourly automated dotfiles backup proves cleanupPeriodDays: 99999 was present continuously from Jan 22 through today. No gap, no reset.
  2. Forensic proof via sessions-index.json: 490 sessions confirmed to have existed (Dec 17 - Feb 4) with metadata intact, but all JSONL files deleted.
  3. Binary analysis: Confirmed .passthrough() schema mode and the protective guard in current versions.

If #16978 is the canonical issue for this bug, it should be reopened rather than closing this as a duplicate of issues that describe different problems.

yurukusa · 3 months ago

Great analysis — the ?? 30 fallback pattern silently becoming destructive when settings fail to load is a real design flaw.
Until this is fixed upstream, here's a hook-based workaround to protect session files:
SessionStart hook (~/.claude/hooks/backup-sessions.sh):

BACKUP_DIR="$HOME/.claude-session-backups"
mkdir -p "$BACKUP_DIR"
find "$HOME/.claude/projects" -name "*.jsonl" -newer "$BACKUP_DIR/.last-backup" 2>/dev/null | while read -r f; do
  rel="${f#$HOME/.claude/}"
  mkdir -p "$BACKUP_DIR/$(dirname "$rel")"
  cp -n "$f" "$BACKUP_DIR/$rel" 2>/dev/null
done
touch "$BACKUP_DIR/.last-backup"

Settings:

{
  "hooks": {
    "SessionStart": [
      {
        "command": "bash ~/.claude/hooks/backup-sessions.sh",
        "timeout": 5000
      }
    ]
  }
}

This runs at the start of each session, so even if cleanup deletes files between sessions, you'll have copies. The cp -n (no-clobber) avoids overwriting existing backups.
For extra safety, you could also add a cron job:

0 */6 * * * rsync -a --ignore-existing ~/.claude/projects/*/sessions/ ~/.claude-session-backups/
Butanium · 3 months ago

Root cause identified: --setting-sources and SDK settingSources bypass cleanupPeriodDays

I hit the same bug (filed as #45735) and traced it through the decompiled v2.1.63 source. The zod validation theory from @r-ef is likely a red herring for users with valid settings files. Here's the actual root cause:

The bug

Any Claude Code process started with restricted setting sources — via --setting-sources local, or the SDK's default settingSources: [] — will run background cleanup that ignores cleanupPeriodDays from ~/.claude/settings.json.

Code path (v2.1.63)

  1. --setting-sources local sets enabled sources to ["localSettings", "policySettings", "flagSettings"]. The SDK defaults to settingSources: [], which results in only ["policySettings", "flagSettings"]. In both cases, userSettings is excluded.
  2. loadSettingsFromDisk() only reads files for enabled sources → ~/.claude/settings.json is never loaded → merged settings have no cleanupPeriodDays
  3. getCutoffDate() falls back: settings.cleanupPeriodDays ?? 3030 days
  4. The safety guard (rawSettingsContainsKey("cleanupPeriodDays")) only checks enabled sources → doesn't find the key → guard doesn't trigger
  5. claude -p and SDK invocations do run startBackgroundHousekeeping() (10 min delay)
  6. --no-session-persistence does NOT disable cleanup
  7. Cleanup walks all of ~/.claude/projects/ globally

Three vectors

  • claude -p --setting-sources local (batch/judging pipelines)
  • SDK invocations with default settingSources: [] (autonomous agents — likely @TweedBeetle's case)
  • Any process inheriting restricted setting sources

Impact

In my case, hundreds of batch claude -p --setting-sources local judging runs each independently triggered a 30-day global cleanup, wiping conversation history across all projects despite cleanupPeriodDays: 1200 being set since Feb 25.

Suggested fixes

  1. Always read cleanupPeriodDays from user settings for cleanup — cleanup is global and should respect global config
  2. Disable background housekeeping in piped/SDK mode — batch processes shouldn't run global cleanup
  3. Make rawSettingsContainsKey check all settings files, not just enabled sources
  4. Scope cleanup to current project, not all of ~/.claude/projects/

🤖 Generated with Claude Code

TweedBeetle · 3 months ago

Confirmed: cleanup is still actively deleting sessions despite v2.1.85+ guard

@Butanium's root cause analysis matches my setup exactly. New evidence since filing:

When I filed this issue (Mar 31): Earliest JSONL was Feb 25
Today (Apr 9): Earliest JSONL is Mar 27 — zero files before that date

Everything from Feb 25 through Mar 26 has been silently deleted over the last 9 days. The 30-day rolling window is actively eating sessions day by day, despite cleanupPeriodDays: 99999 being set and the v2.1.85 guard being present.

My setup runs many autonomous spawned sessions via --dangerously-skip-permissions. These don't pass --setting-sources, but spawn-cc-session.py also doesn't explicitly ensure user settings are loaded for cleanup. If any subprocess inherits restricted setting sources (or if --dangerously-skip-permissions affects the settings load path), that would trigger exactly the code path @Butanium identified.

Immediate mitigations applied:

  • Emergency rsync of all 536 surviving sessions to ~/.claude-session-backups/
  • SessionStart hook that backs up sessions on every session start
  • Cron job every 6 hours for belt-and-suspenders

This confirms the bug is not fixed in current versions. The v2.1.85 guard ("Skipping cleanup: settings have validation errors but cleanupPeriodDays was explicitly set") does not cover the restricted-setting-sources vector. Cleanup continues to run globally with the 30-day default.

+1 to all four of @Butanium's suggested fixes, especially #2 (disable background housekeeping in piped/SDK mode) and #4 (scope cleanup to current project).

cnighswonger · 2 months ago

Adding a data point. I lost approximately 4 months of session data (Nov 2025 – Feb 2026) across 6 project directories. Only sessions from late Feb 2026 onward survived.

Context: these were deep multi-agent sessions for an academic research project — 2,500+ commits produced across the repos during that span, sustained sessions running for 30+ hours with multiple compaction cycles. The JSONL data represented hundreds of dollars of compute and contained irreplaceable reasoning chains.

The ~/.claude/projects/ directories still exist and contain recent sessions, but everything older than ~30 days from the time of loss is gone. I never set cleanupPeriodDays because I didn't know the setting existed — which means the 30-day default ran unchecked.

I'm on v2.1.112 (pinned). The loss likely occurred during one of the upgrades between Dec 2025 and Mar 2026, consistent with the settings-loading fallback behavior described here.

The v2.1.101 changelog entry — "Fixed --setting-sources without user causing cleanup to ignore cleanupPeriodDays" — confirms the bug existed before that version. My sessions were already gone by the time the fix shipped.

Suggestion: session cleanup should require explicit opt-in, not run by default with a silent 30-day retention. At minimum, first-run cleanup after an upgrade should warn before deleting data that predates the upgrade.

EgorBEremeev · 2 months ago
The JSONL data represented hundreds of dollars of compute and contained irreplaceable reasoning chains.

Засудить их к чертовой матери! Во-первых, это реальный ущерб. Во-вторых, сессии это пользовательские данные, которые они никакого права трогать вообще не имеют без явного указания! У меня та же беда - пока месяц занимался здоровьем, все оказалось удалено! Сегодня открыл, хотел продолжить - и пустота! 15 лет назад Evernote также отличились, все удалили на пользовательской стороне!

Amphid0r · 1 month ago

I found this issue after losing months of conversation history on Windows 10 / Cygwin — a platform not yet represented here. I got here via #59248 (Darwin, same loss, same outrage) and #46621 (Linux, systematic forensic documentation). Read both — they're important for the human context they provide.

The tombstone pattern is consistent across all three platforms: session UUID directories surviving with subagent artifacts intact, main .jsonl files gone, .last-cleanup sitting there confirming this was intentional. It's not a bug in the sense of something unintended. Someone wrote that cleanup routine. Someone decided the default behavior would be to delete user data silently.

The fail-unsafe cleanupPeriodDays implementation documented in this issue is where the technical failure becomes a statement about process maturity. The fundamental principle of safety-critical design: when an operation cannot verify its constraints, it does not proceed. It fails safe. STAMP and STPA — system-theoretic approaches to safety, security, and privacy — have existed for over a decade and exist precisely to help teams surface this class of failure before it reaches users. Instead the implementation does the opposite: if settings fail to load, delete the data anyway. That's not an oversight. That's what happens when no one on the team asked "what's the worst thing this code can do, and what happens when it can't verify the conditions under which it's allowed to do it?"

The answer to that question, applied to user data on a user's own machine, is obvious: you do nothing. You do not delete. You do not touch anything. You log a warning and you stop.

I lost conversations that cannot be reconstructed — not code, not artifacts, but thinking done out loud, working context built over months, the kind of thing that exists nowhere else. Other users here have lost the same. We're telling you this because we want it on the record, not because we think a GitHub comment recovers anything.

The question that should be keeping someone at Anthropic awake: how does a development team arrive at a design where silently deleting user-owned files on user-owned hardware is the default, unconfigured, undisclosed behavior? Whose property did they think those files were?

ghh1111 · 1 month ago

+1, 1800+ husks on macOS, v2.1.150, default settings, May 24 cleanup wiped years of work. Have now spent 9+ hours debugging with no way to get them back. Claude code silently deleted all of my /project session jsonl files with no warning, no asking for permission. Then when claude code suggested filing a bug report here, it immediately got flagged by the bot saying it was a duplicate. So how many more people are living through this who are not being allowed to echo the issue here? This is more than an "oops". This is catastrohpic for long term development work if you aren't backing up your local files. I generally have no reason to - claude project files would have been the only reason. Who knew that anthropic would decide all of my chats with all of the logic, reasoning, decisions and why should just be deleted with no warning. I echo the user who said "whose files did they think those were anyway"?
It's wiping out MY DATA and hard work.

BasedGPT · 1 month ago

The root cause documented here is upstream — my toolkit can't fix the settings-loading bypass that's driving the cleanup. But it addresses the file-layer side and might help when restoring from backup.

The thing worth knowing: JSONL files restored to ~/.claude/projects/ without a corresponding local_*.json metadata entry (Windows: %APPDATA%\Claude\claude-code-sessions\, macOS: ~/Library/Application Support/Claude/claude-code-sessions/) can get re-deleted on the next cleanup pass. The cleanup treats unregistered transcript files as orphans. ShreeshaJay documented exactly this pattern on #48334 — restored files survived one session, then disappeared again on the next sync cycle.

To prevent that, I built a tool that synthesises the metadata entries alongside the restore — synth_session_metadata.py in claude-code-session-recovery. It walks ~/.claude/projects/, finds transcript files with no valid metadata pointer, and creates the local_*.json files needed to surface them in the Desktop session list and mark them as registered. Run it right after restoring from backup (rsync, Time Machine, whatever you have) before the next cleanup cycle hits.

It's Windows-tested but the file formats are identical across platforms — the metadata directory path is the only thing that changes. Won't stop the underlying bug from running again, but hopefully it makes the restore step stick. Full write-up at #56172.

BasedGPT · 1 month ago

That massively sucks dude.

When I've hit the pattern you're describing — sessions visible in the Desktop session list but with no conversation history when opened — it usually means the Desktop's metadata layer survived while the JSONL transcript files under ~/.claude/projects/ were deleted. The session list renders from metadata files at ~/Library/Application Support/Claude/claude-code-sessions/ (one local_*.json per session), and those aren't what the cleanup pass targets. That's what produces the "husks": intact metadata pointing at transcript files that no longer exist.

The first thing worth checking is Time Machine. ~/.claude/projects/ is a hidden directory and sometimes falls outside default backup exclusions — if your Time Machine volume covers it and you have a snapshot from before May 24, the JSONL files should be there. Check via the Time Machine UI or tmutil listbackups to see whether a snapshot predates the deletion.

If you do get the transcript files back from backup, I built a tool that handles the re-surfacing step — synth_session_metadata.py in claude-code-session-recovery. It walks ~/.claude/projects/, finds transcript files with no valid metadata pointer, and synthesises the local_*.json entries needed to surface them back in the session list. It's Windows-tested but the file formats are identical on macOS — the scripts need path adjustments for ~/Library/Application Support/Claude/claude-code-sessions/ rather than the Windows path. Full write-up and macOS path notes at #56172.

garrettmoss · 1 month ago

Just made a recovery tool for macOS + Time Machine: https://github.com/garrettmoss/restore-claude-history

Also filed #62272 consolidating this pattern across several threads (yours is linked there) — asking for the setting to actually be honored, or at minimum a warning before deletion.

jkim-git · 1 month ago

Just happened to em as well. I had updated cleanupPeriodDays because I didn't want to lose the data, but it is still lost. This seems serious. How does Anthropic team triage issues like this -- can this receive attention that it needs?

blain3white · 1 month ago

While the root cause is being investigated: I authored Clean My Agent, which backs up Claude Code session files independently of the native cleanup settings — may serve as a manual safety net in the interim.

otalrapha · 1 month ago

Adding an independent confirmation + a new environment/data point (WSL2 → NTFS).

Environment (not yet represented in this thread): Claude Code on WSL2 (Ubuntu) on Windows 11, where ~/.claude/projects is a symlink to a Windows NTFS path (/mnt/c/Users/<user>/.claude/projects, DrvFs/9p). The retention sweep is mtime-based, and DrvFs mtime semantics make the cliff just as sharp here.

Independent root-cause confirmation (binary): reverse-engineering the bundled CLI shows the cutoff resolves as cleanupPeriodDays ?? <DEFAULT=30>. The explicit user setting (cleanupPeriodDays: 3650, valid against the schema) does not reach the merged-settings value read at cleanup time, so the hardcoded 30-day default applies at runtime. This matches @yurukusa's ?? 30 observation and @Butanium's "settings-loading bypass" root cause earlier in this thread.

Empirical signature: a sharp 30–31 day deletion cliff; the setting was present and schema-valid the whole time and was simply ignored. We lost a 2,121-message (~95-day) session this way.

Net: +1 data point on Windows/WSL2, plus independent confirmation that the failure is the ?? 30 fallback at settings-merge time (not a docs/scope issue). The setting being valid-but-ignored is the dangerous part — there is no warning before deletion.

Workaround we deployed (in case it helps others): a daily out-of-tree rsync --update (no --delete) archive of ~/.claude/projects/*.jsonl to a separate disk + encrypted offsite, since the setting can't be relied on.

muellah · 21 days ago

Corroborating report — cleanupPeriodDays: 99999 ignored, captured with a daily boundary monitor

Environment: macOS 26.5.1 (Apple M2). Claude Code 2.1.181 (Claude desktop-app–bundled, the running instance) plus standalone CLI 2.1.179 at the time of the deletions. Sessions launch with --setting-sources=user,project,local, so user is in scope — i.e. the earlier fix for "--setting-sources without user causing cleanup to ignore cleanupPeriodDays" is present in this version, yet deletion still occurs.

Configuration is correct on every axis:

  • ~/.claude/settings.json"cleanupPeriodDays": 99999 (valid JSON, top-level integer)
  • No managed-settings.json, no environment override, no project / local / settings.local.json override

Evidence the default 30-day sweep runs regardless: I keep an append-only mirror of ~/.claude/projects and log the oldest surviving transcript on each run. The oldest-surviving boundary advances to exactly (run date − 30 days) on discrete dates — the signature of the 30-day default firing despite cleanupPeriodDays: 99999:

2026-06-18 09:00 | live_oldest=2026-05-19   <- 2026-06-18 minus 30d
2026-06-25 09:00 | live_oldest=2026-05-19
2026-06-25 11:14 | live_oldest=2026-05-26   <- 2026-06-25 minus 30d

The jumps are not daily; they coincide with app restarts/updates (matching #62272). This is consistent with the sweep falling back to cleanupPeriodDays ?? 30 when user settings aren't loaded during cleanup (fail-destructive).

Request: make the sweep fail-safe — if user settings fail to load, skip cleanup rather than defaulting to 30 — and always resolve cleanupPeriodDays from user settings regardless of launch / --setting-sources context.

davidlovas · 13 days ago

This is the one that worries me most. After losing 201 of my ~302 sessions to the 30-day default cleanup (including a month-long session that held important, long-lived context work), I set cleanupPeriodDays: 3650 to protect what is left. But this issue reports the setting being ignored, with deletion happening anyway, especially around updates and restarts (see #62272).

If that is confirmed, then right now there is no reliable way for a user to prevent silent data loss, which is a serious regression for a tool people use every day. Can a maintainer confirm the current status and whether a fix has shipped? People need to know the setting can actually be trusted. For now I have fallen back on an external daily backup because I cannot rely on it.