Claude Code Session History, Transcripts and How to Keep Them
Claude Code keeps session history as JSONL files and deletes them after 30 days by default. Learn where they live, how to change cleanupPeriodDays, fix a broken resume and archive everything.

Claude Code session history is stored as JSONL transcript files in ~/.claude/projects/<project>/<session-id>.jsonl, and Claude Code deletes them once they are older than cleanupPeriodDays, which defaults to 30. Raise that value in settings.json to keep them longer, mirror the folder into an append-only archive so nothing is lost when the sweep runs, and repair broken transcripts with a backup first when claude --resume fails.
Where does Claude Code store session history?
By default Claude Code writes every session to its own JSONL file. In the path above, <project> is your working directory path with every non-alphanumeric character replaced by a dash, and ~ is your home folder. Each line is a JSON object for a message, a tool use or a metadata entry. Anything that passes through a tool lands in that file as plaintext: file contents, command output, pasted text. If you need to move the whole tree, the CLAUDE_CONFIG_DIR environment variable changes the root.
| Path under ~/.claude/ | Holds | Removed by the age sweep? |
|---|---|---|
| projects/<project>/<session>.jsonl | Full conversation transcript | Yes |
| projects/<project>/<session>/subagents/ | Subagent transcripts | Yes, with the parent session |
| projects/<project>/<session>/tool-results/ | Large tool outputs saved as separate files | Yes |
| file-history/<session>/ | Pre-edit file snapshots for checkpoints | Yes |
| history.jsonl | Every prompt you typed, with timestamp and project | No |
One caveat before you build anything on these files: Anthropic documents the entry format as internal to Claude Code and says it changes between versions, so tools that parse transcripts directly can break on a release. For stable access the docs point to /export and to the hook input field transcript_path, which a SessionEnd hook receives. The details are on Anthropic's session management page.
Why do old Claude Code sessions disappear?
Claude Code runs a retention sweep. It deletes the files in the table above once they are older than cleanupPeriodDays. The default is 30 days and the minimum is 1. The sweep also removes plan files, per-session debug logs, pasted-content caches and the reports written by /insights, and the full list is in the .claude directory reference. Nothing announces the deletion. You find out when you try to resume a session from last month and Claude Code answers This session's saved conversation is no longer on disk.
The sweep is skipped in two cases: a claude -p --bare run, and a state where Claude Code cannot safely work out the retention period, for instance when a settings file cannot be parsed. Outside those cases, assume that anything older than the setting is gone.
How do you change cleanupPeriodDays?
Add the key to your user settings file, ~/.claude/settings.json, so it applies to every project:
{
"cleanupPeriodDays": 365
}
Pick a number of days that matches how long you want history. Setting 0 is not a way to switch the sweep off: it fails with a validation error. After you save, run /status. If the setting or the file has an error, Claude Code shows a warning there and pauses the sweep until you fix it.
Think about the trade before you set a very large number. Transcripts are plaintext copies of everything Claude Code read or ran, which can include secrets that appeared in command output. Longer retention means those copies live longer. Run claude purge to delete a project's transcripts and related state sooner than the sweep would.
Why does claude --resume fail on some sessions?
When people search for claude code resume not working, the cause is usually one of these:
| What you see | Likely cause | What to do |
|---|---|---|
| No conversation found with session ID | The ID matches no stored transcript | Check the ID, or open claude --resume and press Ctrl+A to list every project |
| Failed to resume the conversation | A corrupt or missing transcript, or a file system problem | Confirm the .jsonl file exists and is readable, then repair it (below) |
| EBADF error on Windows | Antivirus or file-locking software touching the transcript | Restart Claude Code and check whether antivirus is scanning the ~/.claude folder |
| This session's saved conversation is no longer on disk | The retention sweep deleted it | Restore it from an archive |
| API Error 400 about thinking blocks | An empty thinking block in the saved messages | Repair the transcript |
The last row comes from the Claude Session Guard README. Claude Code writes one assistant response as several transcript lines that share a message.id, and it merges them back on replay. If any of those lines holds a thinking block whose text is empty or whitespace, resuming the session dies with each thinking block must contain non-whitespace thinking. A second cause is mixing up copies: Anthropic notes that resuming the same session in two terminals without forking interleaves both sets of messages into one transcript. Use /branch or --fork-session when you want a second line of work.
Two resume tricks help here. claude --resume <session-id> works from any directory, because Claude Code searches the current project first and then every other project on the machine. And claude --resume also accepts the absolute path of a .jsonl transcript, which is how you open an archived copy.
How do you keep a permanent archive of Claude Code transcripts?
A larger cleanupPeriodDays only delays the loss. An archive removes it. Claude Session Guard is an MIT-licensed Claude Code plugin, written with the Python 3 standard library only, that keeps an append-only mirror of every transcript, subagent and workflow transcripts included. Set it up in three steps:
- Install the plugin from inside Claude Code:
/plugin marketplace add FiredMosquito831/claude-session-guard /plugin install claude-session-guard@claude-session-guard - Build the initial archive and usage database once. The README says this takes a few minutes on a large history:
sh "$CLAUDE_PLUGIN_ROOT/scripts/run.sh" session_archive sync --full sh "$CLAUDE_PLUGIN_ROOT/scripts/run.sh" usage_db build sh "$CLAUDE_PLUGIN_ROOT/scripts/run.sh" usage_db export - Leave it alone. Hooks keep the archive current. Check it any time with
session_archive verify, which answers whether anything is eating your session data, and usesession_archive restoreto put lost sessions back.
The archive lives in ~/.claude/session-archive/, with the mirrored transcripts/ tree, a usage.db SQLite file, an append-only usage-ledger.jsonl and a reports/ folder of CSV files. It is a real copy, so it grows with your history and can reach several GB. The plugin deliberately avoids hardlinks, because a hardlink shares storage with the live file and a truncation would destroy both copies.
You can build a do-it-yourself version with a SessionEnd hook or a scheduled copy of ~/.claude/projects. Watch for two traps. A plain overwrite replaces a good archived file with a truncated live one, and a copy of only the top-level .jsonl files misses the subagents/ folders.
How do you repair a corrupted transcript safely?
Rewriting a transcript is risky, because Claude Code may be appending to the same file at that moment. Any repair tool should follow these rules. They match the safety properties that the Claude Session Guard README lists for its jsonl_repair pass:
- Take a full backup first. The plugin offers no way to skip it.
- Offer a dry run that reports problems and writes nothing.
- Skip live and recently written transcripts, since rewriting a file under its writer truncates it.
- Abort the rewrite if the file changed between the scan and the write.
- Replace the file atomically: write a temp file, fsync it, then rename it over the original.
- Archive every removed line before the rewrite, and abandon the rewrite if archiving fails.
R="sh $CLAUDE_PLUGIN_ROOT/scripts/run.sh"
$R jsonl_repair --all --dry-run # malformed JSONL, report only
$R api_repair scan # transcripts the API rejects on resume, report only
$R api_repair fix <session-id> # repair one session
$R api_repair fix --all # repair everything eligible
jsonl_repair keeps unparseable lines verbatim and removes only exact duplicates and control-character-only lines, all recoverable from backups/sessions/removed-lines-archive.jsonl. api_repair targets the empty thinking block: it removes that block (or the whole line, if the block was all it held), re-links the parentUuid chain and archives whatever it removed. It runs at SessionEnd, the one moment a transcript is provably not being written to, and a SessionStart sweep catches anything missed.
How do you analyse Claude Code token usage per model over time?
Claude Code token usage analytics need a store that survives the sweep. Claude Session Guard reads its archive into SQLite, one row per usage event, and exports CSV rollups by hour, day, week and month, each with a per-model breakdown, plus sessions.csv, models.csv and a file of Claude's rolling 5-hour windows.
$R usage_db stats # headline totals
$R usage_db daily 30 # last 30 days
$R usage_db models # per-model tokens and cost
$R usage_db sessions 40 # session timeline
$R usage_db sql "SELECT name FROM sqlite_master WHERE type='view'" # list the views
Counting is where most scripts go wrong. Claude Code splits one assistant response across several lines (thinking, text, each tool call) and repeats the same usage object on every one of them, so summing lines over-counts. In the README's own reconciliation, 729,912 transcript lines collapsed into 339,927 logical messages, and a naive per-line sum came out 2.03x the true total. The plugin de-duplicates twice, by line and by message, and keeps the row with the largest token total because the usage object grows as a response streams.
Cost needs one more step, because Claude Code does not record a dollar figure in transcripts. The plugin computes it from token counts and the rates in its model_pricing table, in USD per million tokens, with separate columns for 5-minute and 1-hour cache writes and for cache reads. A model with no row costs 0. Read that as unpriced: free-tier and proxied third-party models show up this way until you add rates. Cache columns matter here, as our prompt caching guide shows. Inside a session, /usage gives an estimate at list price for that session only. The open-source ccusage is a good companion. According to the Session Guard README it reads the live ~/.claude/projects folder, so anything the sweep deleted is absent from its reports. Use both. If you route Claude Code through a gateway such as My Claude Code, its request log records the cost of each request on the gateway side.
Where does Danube Labs fit in?
Danube Labs is a founder-led software and AI studio in Bucharest. It built Claude Session Guard and released it as open source, and the code is on GitHub. The same habits, backups before rewrites, atomic writes and numbers that reconcile, go into the automation work we do for clients. If you have a workflow that deserves that care, book a free intro call.
Frequently asked questions
- Where does Claude Code store conversation history?
- In JSONL files at ~/.claude/projects/<project>/<session-id>.jsonl, one file per session, where <project> is your working directory path with non-alphanumeric characters replaced by dashes. Subagent transcripts sit in a subfolder named after the session. Prompts you type are also logged in ~/.claude/history.jsonl, which the age-based cleanup does not remove.
- How long does Claude Code keep sessions?
- By default 30 days. Claude Code deletes transcripts and related session files once they are older than cleanupPeriodDays, which defaults to 30 and has a minimum of 1. Setting it to 0 fails with a validation error. Change it in settings.json, and use an archive such as Claude Session Guard if you need history to outlive the setting.
- How do I stop Claude Code deleting old sessions?
- Raise cleanupPeriodDays in ~/.claude/settings.json, for example to 365, then check /status for settings warnings. For a permanent copy, keep an append-only mirror of ~/.claude/projects, which is what Claude Session Guard does. Remember that transcripts are plaintext and can contain secrets from command output, so longer retention also means longer exposure.
- Why is claude --resume not working?
- Common causes are a deleted transcript, a wrong session ID, a corrupt transcript file, antivirus interfering with the file on Windows, and an empty thinking block that makes the API return a 400 error on replay. Check that the .jsonl file exists, press Ctrl+A in the picker to widen it, and repair the transcript with a backup before retrying.
- Can I resume a Claude Code session from another folder?
- Yes. claude --resume <session-id> works from any directory: Claude Code looks in the current project and its worktrees first, then in every other project on the machine. You can also pass the absolute path of a transcript file, such as an archived copy, to claude --resume. The claude --continue flag is the one scoped to the current directory.
- How can I see Claude Code token usage by model?
- Inside a session, /usage shows tokens and an estimated list-price cost per model for that session. For history, build a store from your transcripts. Claude Session Guard keeps a SQLite database and writes hourly, daily, weekly and monthly CSV rollups with a per-model breakdown, de-duplicated so each response is counted once.


