Your sessions are in a folder called projects, and it has nothing to do with Projects

July 27, 2026

I went looking for a Claude Code session from the week before. I knew roughly what was in it and I wanted one exact command back out of it. Finding it took twenty minutes, which is embarrassing for someone who works in this stack every day.

The folder is ~/.claude/projects/. I assumed that meant Projects — the claude.ai feature where you group chats around a knowledge base. It doesn't. Different product, different storage, same word. ~/.claude/projects/ is one folder per working directory, and the folder names are mangled paths.

That sent me through the docs for an afternoon. I came out with a map, one genuinely uncomfortable paragraph, and a couple of settings I now set on purpose.

The map

Where Claude sessions are stored A table mapping how a session starts to where it is written and how long it survives, across Claude Code, Cowork, chat, and the API. Colors show the storage realm: your disk, Anthropic's servers, your own backend, or nothing stored. your disk Anthropic's servers your own backend nothing stored How the session starts Where it lands Lifetime CLAUDE CODE COWORK CHAT API · SDK claude — fresh, in a directory ~/.claude/projects/<encoded-cwd>/<id>.jsonl plaintext · every message, tool call, tool result 30 days --continue · --resume /resume the same file — appended same session ID, conversation continues 30 days /branch · --fork-session a new <id>.jsonl beside the original history copied, session IDs rewritten 30 days /cd · entering a worktree Claude-created worktrees the transcript moves with you relocated to the new directory's store 30 days --add-dir storage doesn't move only the picker widens to find it 30 days subagents · large tool output <id>/subagents/ · <id>/tool-results/ sidecars inside the session folder with parent every file Claude edits ~/.claude/file-history/<session>/ pre-edit snapshots for /rewind 100 checkpoints · 30 days every prompt you type ~/.claude/history.jsonl with its project path, for up-arrow recall never swept -p --no-session-persistence CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 nothing written · not resumable Cowork — remote (default) saved to your Claude account per-session sandbox, destroyed at end account-managed Cowork — local mode on-device VM no path documented undocumented claude.ai · desktop · mobile Anthropic's servers delete in the UI is a soft delete purged ≤30 days after you delete Messages API nothing — stateless you resend the history every call Agent SDK — default the same ~/.claude/projects/ JSONL 30 days Agent SDK + SessionStore mirrored to S3 · Redis · Postgres a mirror — local disk is still written first your rules bensimon.dev
Click to enlarge

The tree

Claude Code writes one plaintext JSONL file per session:

~/.claude/projects/<encoded-cwd>/<session-id>.jsonl

The encoding is blunt: take the absolute working directory, replace every non-alphanumeric character with a hyphen. /Users/ben/Source/myrepo becomes -Users-ben-Source-myrepo. It's lossy, so you can't reverse it, and that leading hyphen is your leading slash.

Each line is one event — a message, a tool call, a tool result. Sidecars live inside a folder named for the session: <session-id>/subagents/ for subagent conversations, <session-id>/tool-results/ for outputs too big to inline. Siblings elsewhere in the tree hold the rest: file-history/<session>/ keeps pre-edit snapshots so /rewind can put files back, plus plans/, debug/, session-env/, tasks/.

Two more worth knowing about. ~/.claude.json holds application state, auth state, and your personal MCP configuration — a review that only looks at projects/ misses it. And ~/.claude/history.jsonl. That one isn't per-session. It's every prompt you have ever typed, with its project path, so up-arrow recall works. Hold that thought.

Two keys, one tree

Here's the part that cost me the twenty minutes.

Transcripts key on the working directory. Auto memory — the notes Claude writes to itself — lives in the same projects/ tree but keys on the git repository.

So with worktrees: three worktrees of one repo give you three transcript folders and one shared memory folder, sitting next to each other in the same parent directory, under two different addressing schemes, with nothing in the folder names to tell you which is which.

I don't think that's wrong, exactly. You want memory to follow the repo and transcripts to follow where you were standing. But it means "where's my session" and "where's my memory" have different answers, and the folder name doesn't hint at either.

Verified, mostly

A reader asked whether I'd tested the worktree claim or just read it. Fair hit — I'd read it. So I tested it.

Two worktrees off the same repo, a session in each. Two transcript folders appeared, one per working directory, named for the encoded path. That half is confirmed on disk.

Then I asked Claude, running inside worktree A, to save something to memory. It reached for the repo's memory directory, not the worktree's — and said so itself, naming the mismatch between the memory key and the session root. The directory showed up where the repo key predicted and nowhere else. So the two keying schemes are real and observable.

What I still can't claim is shared recall end to end — write a marker in one worktree, read it back from another — because the write got blocked. By my own supervisor, for being outside the project root.

Which is a finding I wasn't looking for. ~/.claude/ is outside every project root by definition. If you run Claude Code under anything that confines file writes to the repo, auto memory can't write at all. Ask it to remember something and it will tell you it couldn't — mine did. But most memory writes are the agent's own initiative, and a refused background write is not something you are going to notice. What you see is a memory that never accumulates, with no obvious reason why. Mine had apparently been doing that for a while: one memory directory on the whole machine, and it predated the rule.

What each entry point does

The map has the full set. The parts worth internalizing:

Resume appends. Fork copies. --continue and --resume reopen the same session ID and keep appending to the same file. /branch and --fork-session create a new session ID with the history copied in and the IDs rewritten, leaving the original alone. Gotcha: permissions you approved "for this session" don't come with you into the branch.

/rewind is murkier. It restores files and conversation to an earlier point, and the confirmation text says the conversation will be forked — but there is an open issue reporting that it mutates the session in place and the original stops appearing in the picker. I haven't reproduced it carefully enough to tell you which behavior you'll get on your version. If you want the old path kept for certain, branch first.

/cd moves the file. Change directory mid-session and the transcript relocates to the new directory's store. Same when you enter a worktree Claude created. That's correct behavior and it still surprised me — the session I was hunting had moved because I'd cd'd hours earlier.

--add-dir moves nothing. It widens what the picker can find; storage stays where it started. Worth knowing before you go digging in the wrong folder. The real fix for "where did it go" is in the picker itself: Ctrl+W widens to every worktree of the repo, Ctrl+A to every project on the machine.

Print mode can write nothing. -p --no-session-persistence leaves no transcript and can't be resumed. CLAUDE_CODE_SKIP_PROMPT_HISTORY=1 skips the transcript and the prompt history.

The paragraph everyone should read

From Anthropic's own documentation:

Transcripts and history are not encrypted at rest. OS file permissions are the only protection. If a tool reads a .env file or a command prints a credential, that value is written to projects/<project>/<session>.jsonl.

That's a plain, honest statement and I'd much rather have it stated than discovered. But sit with it for a second. Every file your agent read. Every command's output. Every environment variable that got echoed while you were debugging something at 11pm. Plaintext, in your home directory, for thirty days by default.

I get why it works this way. Resume needs the history, /rewind needs the snapshots, and encrypting it would mean key management nobody asked for. Fine. It just means the transcript tree deserves the same handling as anything else credential-adjacent on your machine: know it's there, keep it off synced or shared volumes, and treat handing someone your laptop the way you'd treat handing them your shell history.

The docs name three mitigations: lower cleanupPeriodDays, set CLAUDE_CODE_SKIP_PROMPT_HISTORY, and use permission rules to deny reads of credential files. The third one is the only one that addresses the cause.

Cowork stores sessions in one of two places

Cowork is the odd one out on the map, because where your session lands depends on which execution mode you're in.

Remote, which is the default. The agent loop and code execution run on Anthropic's servers, and sessions and files are saved to your Claude account. Each session gets its own sandbox, created when it starts and destroyed when it ends. Nothing lands in ~/.claude/, and your sessions follow you between devices.

Local mode. The agent loop runs on your machine and code executes in an on-device VM. Anthropic documents the architecture but doesn't publish where a local session's data is written, so I'm not going to guess at a path for you.

Why both exist is the usual tradeoff. Remote buys portability and keeps nothing on your disk, at the cost of your work living in someone else's infrastructure. Local keeps execution on hardware you control, at the cost of a footprint you're then responsible for. Remote became the default in July 2026, following a disclosed sandbox escape in local mode.

One governance note, because it surprises people: Cowork activity is excluded from audit logs, the Compliance API, and data exports. There is a separate OpenTelemetry stream for Team and Enterprise carrying prompts and tool invocations — but that's operational telemetry, not audit evidence. In a regulated shop that distinction is the whole ballgame.

Thirty days, except the file that's forever

cleanupPeriodDays defaults to 30. It runs at startup and sweeps transcripts, subagent transcripts, tool-results, file-history, plans, debug, caches. Minimum is 1. Setting it to 0 is a validation error, not "off." A commenter pointed out the obvious corollary I never spelled out — it goes up as well as down:

// .claude/settings.json
{ "cleanupPeriodDays": 365 }

history.jsonl isn't in the sweep. So your transcripts age out on schedule while the index of every prompt you've ever typed, and the project each one was typed in, does not.

If you want a project actually gone, claude project purge handles one properly — transcripts, auto memory, per-session data, and the matching lines in history.jsonl. That last part is why it beats rm -rf.

What I'd change today

Set retention on purpose. Thirty days is a default, not a recommendation. I put the question to GPT and Gemini before writing this, and they converged from opposite directions on about a week for ordinary work. The argument holds up: transcript value collapses fast — you ask "why did it touch that file" during the task or at review, almost never a month later — while exposure accrues the entire time. Both also noted that a seven-day transcript policy sitting next to a permanent history.jsonl is internally inconsistent, which is fair. If you’re setting this for a team, set it in managed settings rather than asking each developer to remember.

Deny credential files at the permission layer. Anything the agent can't read can't land in the transcript. That's upstream of every other mitigation.

Know the escape hatch before you need it. For a one-off task that will touch secrets, -p --no-session-persistence writes nothing at all.

One more, for anyone tempted to build tooling on these files: don't parse the JSONL. The docs say outright that the entry format is internal and changes between versions. Use /export, --output-format json, or the transcript_path that gets handed to hooks.

The lesson

Your sessions are files. They're plaintext, they're keyed by a mangled path, they move when you cd, and one of them never expires. None of it is hidden — it's all documented — but the folder name pointed me the wrong way for a week, and I doubt I'm the only one.

Know where they are. Decide how long they live. Assume anything your agent read is in them.

SOs

SOs — standing orders you can lift and prime your own agent with. (New here? The convention started with the Neverland post.) Paste them into a session for a one-time dose, or commit them to your CLAUDE.md / AGENTS.md and they stay in force.

STANDING ORDERS — Session hygiene
Source: "Your sessions are in a folder called projects" — bensimon.dev

1. Before running anything likely to print a secret — env dumps, config
   reads, verbose auth output — say plainly that the value will be written
   to the plaintext session transcript, and offer a way to avoid it.

2. When asked where a session lives, give the exact path shape:
   ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl. Note that /cd
   and entering a worktree relocate it, and that the picker widens with
   Ctrl+W (worktrees) or Ctrl+A (all projects).

3. Never parse session JSONL directly to answer a question. The format is
   internal and changes between versions. Use /export, --output-format
   json, or the transcript_path handed to hooks.

4. When a task will handle credentials, propose starting it with
   -p --no-session-persistence or CLAUDE_CODE_SKIP_PROMPT_HISTORY=1
   BEFORE the work starts, not after.

5. If asked to clean up session data, name history.jsonl explicitly — it
   is not covered by the age-based sweep — and prefer
   `claude project purge` over manual deletion.

6. When reporting on retention, state the cleanupPeriodDays value in
   effect and what it does not cover. Never imply that deletion in a UI
   or a sweep means the data is gone everywhere.

7. If this session's file access is confined to the project root — a
   sandbox, a supervisor, a deny rule — say so before offering to
   remember anything. ~/.claude/ is outside every project root by
   definition, so auto memory cannot be written. An explicit request
   surfaces the refusal; a background write does not.