gowtham sai
Claude Code

Why you cannot copy a Claude Code session to another machine

6 August 2026 · 6 min read

You get a new laptop, copy ~/.claude across, run claude --resume, and either nothing shows up or the conversation opens against files that aren't there. I lost an afternoon to this before I worked out what was going on, and the reason turns out to be much duller than the symptom suggests.

Where sessions actually live

Everything Claude Code knows about you sits under ~/.claude, and the part that matters here is projects/:

~/.claude/
  .claude.json                 # config, including a per-project map
  projects/
    -Users-alex-work-dashboard/
      44ab90ee-cc33-4d22-....jsonl
      8e3d21d1-1a90-4f77-....jsonl
    -Users-alex-work-acme-api/
      1f9c77a2-bb22-4e11-....jsonl

That folder name isn't a hash or an id, which is the first surprise. It's the absolute path of your project with the slashes swapped for hyphens, so /Users/alex/work/dashboard becomes -Users-alex-work-dashboard. When you run claude --resume, Claude Code encodes whatever directory you're standing in and goes looking for a folder with that name. Each .jsonl inside is a single session, one JSON object per line, appended as you talk.

Three things break when you copy it

The folder name is the obvious one. Your username is different on the new machine, or the project lives somewhere else entirely, and now -Users-alex-work-dashboard describes a path that doesn't exist. Claude Code encodes your current directory, finds no folder matching it, and shows you an empty history. Nothing is corrupted and nothing is lost, it's just looking in a place you didn't put anything.

Rename the folder and you hit the second problem, which is that every line of the transcript carries the old path inside it:

{"type":"user","cwd":"/Users/alex/work/dashboard","message":{...}}

So the session appears in the list and opens, but the conversation still believes it's running somewhere it isn't. Anything that referred to a file resolves against a directory that went away with your old laptop. Then there's .claude.json, which keeps its own projects map keyed by absolute path and holding per-project state, so it needs the same treatment or the session turns up without the settings that surrounded it.

Which is really one problem wearing three hats: a session isn't portable data, it's data plus an assumption about where the filesystem is. Everything below follows from that.

Fixing it by hand

None of this is hard, it's just tedious, and if you're moving one project you may as well do it manually. Say you're going from /Users/alex/work/dashboard on a Mac to /home/sam/dev/dashboard on a Linux box:

# 1. copy the project folder, renamed for the new path
cp -R "~/.claude/projects/-Users-alex-work-dashboard" \
      "~/.claude/projects/-home-sam-dev-dashboard"

# 2. rewrite the cwd recorded inside every transcript
cd ~/.claude/projects/-home-sam-dev-dashboard
sed -i '' 's#/Users/alex/work/dashboard#/home/sam/dev/dashboard#g' *.jsonl

# 3. re-key the project entry in .claude.json
#    (open it and rename the key under "projects")

cd into the project on the new machine, run claude --resume, and your history is back. Your login won't come across and shouldn't, since credentials are bound to the machine that issued them, so log in once before you resume.

Why that sed line is a trap

The moment Windows is involved at either end, the replacement above silently does nothing, and it took me longer than I'd like to admit to see why. Transcripts are JSON, and JSON escapes backslashes, so the path in the file doesn't look like the path you typed:

on disk:            "cwd":"C:\\Users\\alex\\work\\dashboard"
what you searched:  C:\Users\alex\work\dashboard

Doubled in the file, single in your pattern, no match, no error, and a transcript that looks rewritten until you actually open it. The fix is to build both halves of the replacement by JSON encoding the paths first, which gives you exactly the escaped form the file contains. On Unix the escaped form is identical to the raw one, so the same code is correct going either direction, which is the sort of thing you only notice once it has already bitten you.

After that come the smaller ones, and there are more than you'd expect. Paths turn up inside message bodies as well as in cwd. macOS is case insensitive and Linux isn't. A project that lived under a temp directory on the old machine is meaningless on the new one. And if something already exists at the destination you have to decide between merging and overwriting, because overwriting quietly is how people lose conversations they wanted to keep.

Codex and opencode store it differently again

If you've got more than one agent on the go, none of the above transfers, and each one fails in its own way.

Codex writes a "rollout" per conversation to $CODEX_HOME/sessions/YYYY/MM/DD/rollout-<timestamp>-<uuid>.jsonl, with $CODEX_HOME defaulting to ~/.codex. The project path lives in a cwd field inside the file, same as Claude Code, but the layout works against you in two ways. Sessions are bucketed by date rather than by project, so there's no folder to rename and no way to find everything belonging to one repo without opening every file and reading it. And some rollouts are zstd compressed, which means grep and sed skip straight past them without complaining. If you're doing this by hand, decompress first or you'll convince yourself the sessions aren't there.

One more thing worth knowing if you go digging: the same conversation is recorded twice in each rollout. response_item lines are the model-facing history and event_msg lines are a parallel stream for the UI. None of this layout is documented upstream, so everything I know about it came from reading real rollout files against the Codex source.

opencode skips files altogether and keeps sessions in SQLite, normally at ~/.local/share/opencode/opencode.db, with the project path in a directory column on the sessions table. That makes the text-editing approach useless, since there's nothing to sed, and it makes writing to the file directly a bad idea while opencode might have it open. The safer route is opencode's own CLI, which will run SQL for you:

opencode db "UPDATE session SET directory = '/home/sam/dev/dashboard'
             WHERE directory = '/Users/alex/work/dashboard'"

Back up the database first. It's one file, so a copy costs nothing and an UPDATE without a WHERE clause is the kind of mistake you only make once.

Same conceptual problem three times over, with three unrelated storage shapes to learn and three different ways to get it wrong.

The tool I built for this

I got tired of doing the manual version and wrote entangle, a single Go binary that handles the rewriting and moves sessions between machines. Handing one to a teammate goes over magic-wormhole, so what you exchange is three words rather than a file:

you:   entangle send <session-id>
       -> 4-october-stormy

them:  entangle receive 4-october-stormy

It lands in their project with every path rewritten for their machine and their operating system, the transfer is encrypted end to end, nothing is uploaded anywhere and there's no account involved, and likely secrets are masked on the way out. If you're moving your own history to a new laptop instead, entangle export and entangle import do the same rewriting in bulk. It reads Claude Code, Codex and opencode.

What it doesn't do

Three limits worth stating plainly, because they come up every time. Whole-machine export only reads the Claude Code layout so far, though sharing a single session works across all three tools. You can't open a Claude Code session inside Codex, and that one isn't a roadmap item so much as a thing I decided not to fake: assistant turns carry reasoning content that's opaque or signed per vendor, and inventing it gives you a transcript that reads authoritatively and misrepresents what the model actually did. And the secret masking matches credential shapes I could enumerate, which means it will miss anything with no distinctive format, which is why the bundle stays a file you can open and read before you trust it.


Source is at github.com/gowtham-sai-yadav/entangle, MIT licensed, one binary on macOS, Linux and Windows. If your team runs into this differently to how mine did, I'd like to hear about it.