[BUG] Background-fleet "deleteJob" silently unlinks main session JSONL despite cleanupPeriodDays: 36500
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet (related but distinct: #41458, #59248, #59848)
- [x] This is a single bug report
- [x] I am using the latest version of Claude Code (2.1.143, 2.1.144 just downloaded)
What's Wrong?
The background-fleet ("bg-spare") session lifecycle introduced in Claude Code 2.1.x silently deletes the main session JSONL from ~/.claude/projects/<encoded-cwd>/<UUID>.jsonl when a job is claimed/settled, even though cleanupPeriodDays: 36500 is set. The companion directory ~/.claude/projects/<encoded-cwd>/<UUID>/ (containing subagents/, tool-results/, etc.) is intentionally preserved — leaving an orphaned directory with no main transcript.
This is not the same as the TTL-based retention path covered in #41458 / #59248. The retention cleanup respects cleanupPeriodDays; this lifecycle path does not.
Observed scope: 374 out of 1462 sessions (≈25%) are now orphaned across all projects on my machine.
Evidence
1. Setting is correctly applied:
// ~/.claude/settings.json
{
"cleanupPeriodDays": 36500,
"env": { "DISABLE_AUTO_COMPACT": "1" }
}
2. Inventory across ~/.claude/projects/:
| Metric | Count |
|---|---|
| Total session dirs <UUID>/ | 1016 |
| Total main JSONLs <UUID>.jsonl | 1462 |
| Orphans (dir exists, sibling .jsonl missing) | 374 |
3. Reproducible case — session 1789cd89-4253-40f8-a063-ea3fc8f35778:
~/.claude/projects/-Users-davidgonzalez-Workspace-repos-Certainty/:
1789cd89-4253-40f8-a063-ea3fc8f35778/subagents/...exists (dir mtime 2026-05-16)1789cd89-4253-40f8-a063-ea3fc8f35778.jsonlmissing
Out-of-band S3 backup (taken by my own /guardar-historial-equipo hook before kill) confirms the JSONL existed and was actively growing across three days:
2026-05-16 20:45 852 KB conversacion.jsonl
2026-05-17 16:34 1.7 MB conversacion.jsonl
2026-05-18 09:29 1.9 MB conversacion.jsonl ← last snapshot
4. Daemon log shows the kill that immediately preceded the local deletion:
~/.claude/daemon.log
[2026-05-18T14:20:43.986Z] [bg] bg claimed-spare 1789cd89 (fleet)
[2026-05-18T14:29:50.619Z] [bg] bg settled 1789cd89 (killed)
That settled killed (14:29:50Z = 09:29:50 local) matches the timestamp of the final S3 snapshot (09:29:32 local) within ~20 s. After this moment, the local .jsonl is gone but the dir with subagents/ remains.
5. Strings in the binary ~/.local/share/claude/versions/2.1.143 (Mach-O arm64, sha hidden, 197 MB) confirm the mechanism is deliberate:
[PERF:bg-spare-start]
[PERF:bg-spare-spawned]
[PERF:bg-claim-start]
[bg-spare] claim miss (
tengu_bg_spare_claim_fail
job_claim_spare_delete_failed
[bg-spare] deleteJob unconfirmed (
dir preserved
job_respawn_kill_unconfirmed
.jsonl
The literal dir preserved warning, alongside deleteJob, shows the fleet code path explicitly tries to delete .jsonl and explicitly keeps the sibling directory.
6. The retention-cleanup code path is a different one that does respect cleanupPeriodDays. Around offset 211987 in the strings dump:
Skipping retention cleanup: userSettings source is disabled (--setting-sources) and no enabled source provides cleanupPeriodDays.
Skipping cleanup: settings have validation errors but cleanupPeriodDays was explicitly set. Fix settings errors to enable cleanup.
cleanupPeriodDays must be at least 1. To keep transcripts for a long time, set a large number (e.g. 3650 for ~10 years).
That retention path iterates over baseLogs, mcp-logs-, cc-transcript-*.txt, shell-snapshots, feedback-bundles, cc-transcript-*.txt, dump-prompts-*.jsonl, etc. — but the main session JSONLs are also deleted via the unrelated fleet deleteJob path, which neither logs to daemon.log with a recognizable cleanup tag nor honors the user's retention setting.
Steps to Reproduce
- Set
"cleanupPeriodDays": 36500in~/.claude/settings.json. - Use Claude Code 2.1.x with background-fleet enabled (default since 2.1.142). Open and resume the same session across multiple days via
--resume <UUID>or the Agents view. Make sure subagents are spawned so the<UUID>/subagents/dir is populated. - End or close the session.
- Inspect
~/.claude/projects/<encoded-cwd>/:
<UUID>/subagents/...— present<UUID>.jsonl— deleted
Expected Behavior
cleanupPeriodDays: 36500 should keep main session JSONLs on disk regardless of which lifecycle path closes the session. The fleet deleteJob should not unlink the persistent transcript that the user expects to keep for cleanupPeriodDays days.
Actual Behavior
Fleet deleteJob silently unlinks <UUID>.jsonl regardless of cleanupPeriodDays. No log line is emitted to daemon.log explaining the deletion; the only nearby line is bg settled <UUID> (killed). The sibling directory is preserved (intentionally per binary string dir preserved), which makes the deletion look more like data corruption than a retention sweep.
Impact
- 25% of historical sessions on my machine are now orphaned locally.
- For users without an out-of-band backup (in my case
/guardar-historial-equipomirrors to S3 before each kill), the conversations would be permanently lost. --resume <UUID>against an affected session fails with no transcript.
Environment
- macOS Darwin 25.3.0 (arm64)
- Claude Code 2.1.143 (active), 2.1.144 (downloaded)
- Plan: paid; daemon mode enabled; using Agents view + multi-pane workflows extensively
- No iCloud sync on
~/.claude/ - No user hooks touch
~/.claude/projects/
Suggested Fix
In the fleet lifecycle (job_claim_spare, bg settled killed), do not unlink the main session JSONL. Either:
- Leave the JSONL on disk and let the retention sweep (
hi3()/cleanupPeriodDays) decide when to delete it, or - Move the JSONL to an "archived" location instead of deleting, or
- Honor
cleanupPeriodDaysinsidedeleteJob.
Related
- #41458 —
cleanupPeriodDays: 99999 ignored(TTL path; this issue is the fleet path) - #59248 — Silent retention cleanup (no warning/opt-in)
- #48334 — Desktop app deletes session history
- #59848 — Interactive sessions classified as background jobs post-2.1.139 (likely upstream cause: foreground sessions now travel the fleet path)
CC: not applicable — repro is on my account but the mechanism is generic.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗