Shabash
Blog · Claude Code guides

Where are Claude Code sessions stored?

Short answer

In ~/.claude/projects/<project>/<session-id>.jsonl, one plain text file per conversation. Claude Code deletes each one after 30 days by default. Add "cleanupPeriodDays": 3650 to ~/.claude/settings.json to keep them ten years.

By Robin Mehta · September 27, 2026 · updated October 28, 2026

My Mac has over 7 GB of Claude Code conversations sitting in one folder, in plain text. Every one of them changed in the last 30 days.

That's over 9,000 files, but only about 125 are sessions I started myself in the terminal. The rest come from scripts that call claude -p. Anything older is gone, because Claude Code deletes conversations after 30 days.

The prompts I typed are in a different file that the cleanup never touches. So for most old sessions I still have my questions, but none of Claude's answers.

My count of 920 sessions runs to September 25. Two days later, only 100 of those 920 still had a transcript. The other 25 or so of the 125 on disk are sessions I started after September 25.

Conversations live in ~/.claude/projects

~/.claude/projects/<project>/<session-id>.jsonl

<project> is the folder you started claude in, with every character that isn't a letter or digit turned into -. So /Users/sam/code/checkout becomes -Users-sam-code-checkout.

Each line of the file is one JSON object: your message, Claude's reply, a command Claude ran and what it printed, or a bit of metadata like the session's title. When Claude hands a side task to a helper (a subagent), the helper's conversation goes in a <session-id>/subagents/ folder next to the file.

history.jsonl keeps every prompt you typed

~/.claude/history.jsonl

One line per prompt, with a timestamp, the project path and the session ID. The up arrow and Ctrl+R search through this file. The session ID means you can go from an old prompt straight back with claude --resume <session-id>, as long as the conversation file still exists.

The 30-day cleanup doesn't touch it. Mine goes back to January, and it's the longest record I have of what I worked on in Claude Code.

30-day cleanup deletes transcripts, keeps your prompts

Claude Code runs the cleanup in the background after a session starts. These are the paths most people care about, all under ~/.claude (the full list):

PathWhat's in itAfter 30 days
projects/<project>/<session-id>.jsonlThe whole conversationDeleted
projects/<project>/<session-id>/subagents/Subagent conversationsDeleted
file-history/<session-id>/Copies of files from before Claude edited them, used by rewindDeleted
plans/Plans from plan modeDeleted
paste-cache/Long text you pasted inDeleted
history.jsonlEvery prompt you typedKept
projects/<project>/memory/Claude's notes about that project (auto memory)Kept

On my Mac, age counts from the last time the file changed: sessions I started more than 30 days ago but came back to are still there. Sessions you started or last continued in the Claude Desktop app or Cowork are kept at any age, from Claude Code 2.1.248 on. The exceptions: you set desktopSessionCleanupPeriodDays, or your company's managed settings set cleanupPeriodDays.

Keep sessions longer with cleanupPeriodDays

It's one line in ~/.claude/settings.json, next to whatever is already there:

"cleanupPeriodDays": 3650

That's ten years. Claude Code rejects 0, so a big number is how you say "keep them". It won't bring back sessions that are already gone; why Claude Code sessions disappear covers getting those back from a backup.

Shabash, the Mac app I'm building, keeps its own copy of the sessions you typed in, and reopening one from Shabash brings it back after Claude Code deletes it, as long as its project folder is still there. Sessions deleted before you installed it stay gone.

API keys end up in transcripts

The files aren't encrypted. Anthropic's docs say it plainly: if Claude reads a .env file, or a command prints a credential, that value is written into the transcript. Only your Mac's file permissions protect it. If you keep sessions for ten years, any key in them stays on disk that long too.

Do these in order. Steps 1 to 3 make it much harder for Claude to read your .env files and your AWS and SSH keys, step 4 finds keys already in your transcripts, and step 5 is only for wiping a project.

None of them stop a command you allowed from printing a key, like printenv showing an API key from your shell. That output still goes into the transcript, which is why step 4 is there.

  1. Stop Claude opening your key files. Add these rules to ~/.claude/settings.json, or add the four entries to your deny list if you already have a permissions block:

    "permissions": {
      "deny": ["Read(**/.env)", "Read(**/.env.*)", "Read(~/.aws/**)", "Read(~/.ssh/**)"]
    }

    Now Claude Code refuses when Claude tries to open those files with its Read tool, or with commands like cat and head that name the file. Its search tools, Grep and Glob, follow the rules only on a best-effort basis, and the sandbox in step 2 doesn't cover them. The **/ covers a .env in the folder you started claude in, or any folder below it.

  2. Block scripts from your .env files and AWS and SSH keys with the sandbox. A command that doesn't name the file, like grep -r over the whole folder, still gets past step 1, and so does a Python or Node script that opens it itself, Anthropic's docs say. The sandbox stops them. Add this to ~/.claude/settings.json, next to your permissions block:

    "sandbox": {
      "enabled": true,
      "filesystem": { "denyRead": ["~/.aws", "~/.ssh", "~/**/.env", "~/**/.env.*"] }
    }

    ~/**/ covers a .env in any folder under your home folder, but not one outside it. A dev server that loads your .env can't read it inside the sandbox either, so Claude may try to run it outside, which step 3 covers.

  3. Make Claude ask before it leaves the sandbox. When a command fails inside it, Claude may run it again outside, where denyRead doesn't apply. The retry goes through your normal permission rules, so in Manual mode, where you approve commands yourself, you get a prompt.

    But in auto mode, where Claude Code approves commands for you, it can approve that retry without asking. Add this next to your deny list, and Claude Code asks you before every retry outside the sandbox, even in auto mode:

    "ask": ["Bash(dangerouslyDisableSandbox:true)"]
  4. Check for keys that already leaked into your transcripts. This lists every file under ~/.claude holding something shaped like an AWS access key or a GitHub token. It prints file names, never the keys:

    grep -rlE 'AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|github_pat_[A-Za-z0-9_]{80,}' ~/.claude

    Long-term AWS access key IDs start with AKIA. On GitHub, ghp_ starts a classic token and github_pat_ a fine-grained one. Other services' keys have other shapes, so this won't find them.

    If a file shows up, rotate that key first. Deleting the file doesn't help if a backup already has it.

  5. To wipe what Claude Code holds for one project, preview it first:

    claude project purge ~/code/checkout --dry-run

    Run it again without --dry-run and it asks before deleting that project's transcripts, memory, lines in history.jsonl and other per-session files. Images you pasted into those sessions sit in Claude Code's temp folder instead, and the 30-day cleanup deletes them later.

/resume search finds old sessions

Inside Claude Code, /resume opens the session picker. Start typing to search. Ctrl+A widens it to every project on the machine, Ctrl+W to every worktree of this repo, and Ctrl+B narrows it to the current git branch. Ctrl+R renames the highlighted session so it's easier to find next time.

To find the session that opened a pull request, paste the PR's URL into the search, or run claude --from-pr 1234 with your PR number.

Sessions started with claude -p never show up in the picker. You can still reach one by passing its session ID to claude --resume <session-id>, which works from any folder in Claude Code 2.1.223 or later.

From the shell, this lists every conversation changed in the last two days, across all projects:

find ~/.claude/projects -maxdepth 2 -name '*.jsonl' -mtime -2

-maxdepth 2 leaves out the subagent files, which would otherwise swamp the list.

Read sessions from a script with claude -p or a hook

Anthropic's sessions docs say the transcript format is internal and changes between versions, so a script that parses these files can break on any release. These are the supported ways in:

  1. Ask an old session a question and print the answer:

    claude -p --resume <session-id> --output-format json "summarize what we changed" | jq -r '.result'

    --output-format json also returns the session ID, usage and cost.

  2. Copy each conversation as it closes, with a hook (a command Claude Code runs by itself when something happens). This one goes in ~/.claude/settings.json and copies the transcript into ~/claude-backup whenever a session ends:

    "hooks": {
      "SessionEnd": [{ "hooks": [{ "type": "command",
        "command": "mkdir -p ~/claude-backup && cp \"$(jq -r .transcript_path)\" ~/claude-backup/",
        "timeout": 30 }] }]
    }

    Claude Code hands every hook the transcript's path as transcript_path, and jq pulls it out. All SessionEnd hooks share 1.5 seconds, so a long conversation could be cut off mid-copy; "timeout": 30 raises that to 30 seconds. If you already have a hooks block, add the SessionEnd entry to it.

To save one conversation as readable text by hand, type /export notes.txt inside it (how to export a Claude Code conversation, with a script for Markdown). Without a file name, /export opens a menu that can copy it to your clipboard instead.

If you do read the files directly, check for the fields you use and stop with an error when they're missing.

CLAUDE_CONFIG_DIR moves sessions to another folder

Set CLAUDE_CONFIG_DIR in your shell and Claude Code keeps your settings, sessions and plugins there instead of ~/.claude. Anthropic's example runs a second account side by side:

alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'

Point CLAUDE_CONFIG_DIR at a folder and everything above moves with it, including the 30-day cleanup.

Browsing those files instead of finding them

Once you know where the files are, a picker beats find. claude --resume and claude agents cover most of it, and the rest is in every Claude Code session manager worth knowing, built in and third-party. For the other agents: how to resume a Codex session, which keeps its sessions until you delete them, how to resume a Gemini CLI session, which deletes the ones you haven't touched in 30 days, and where Cursor chat history is stored.

Questions

Where are Claude Code conversations saved?

In ~/.claude/projects/<project>/<session-id>.jsonl, one file per session. <project> is the folder you started claude in, with every character that isn't a letter or digit turned into -.

How long does Claude Code keep sessions?

30 days by default. To keep them longer, add "cleanupPeriodDays": 3650 to ~/.claude/settings.json. Setting it to 0 is not allowed.

Where is my Claude Code prompt history?

In ~/.claude/history.jsonl, one line per prompt you typed, with a timestamp and the project path. The 30-day cleanup never deletes it.

Can I move where Claude Code stores sessions?

Yes. Set the CLAUDE_CONFIG_DIR environment variable, and Claude Code keeps your settings, sessions and plugins in that folder instead of ~/.claude.

Is it safe to parse the transcript files?

Anthropic says the format is internal and changes between versions, so a script that parses it can break on any release. Scripts should use claude -p --resume <session-id> --output-format json, or a hook that reads transcript_path. To save one conversation by hand, type /export inside it.