<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gowtham Sai G</title>
    <description>The latest articles on DEV Community by Gowtham Sai G (@gowtham-sai-yadav).</description>
    <link>https://dev.to/gowtham-sai-yadav</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4064948%2F807ac458-41b1-4c2d-810a-d21dc3704674.png</url>
      <title>DEV Community: Gowtham Sai G</title>
      <link>https://dev.to/gowtham-sai-yadav</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gowtham-sai-yadav"/>
    <language>en</language>
    <item>
      <title>Why you cannot copy a Claude Code session to another machine</title>
      <dc:creator>Gowtham Sai G</dc:creator>
      <pubDate>Thu, 06 Aug 2026 13:36:38 +0000</pubDate>
      <link>https://dev.to/gowtham-sai-yadav/why-you-cannot-copy-a-claude-code-session-to-another-machine-2008</link>
      <guid>https://dev.to/gowtham-sai-yadav/why-you-cannot-copy-a-claude-code-session-to-another-machine-2008</guid>
      <description>&lt;p&gt;You get a new laptop, copy &lt;code&gt;~/.claude&lt;/code&gt; across, run &lt;code&gt;claude --resume&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where sessions actually live
&lt;/h2&gt;

&lt;p&gt;Everything Claude Code knows about you sits under &lt;code&gt;~/.claude&lt;/code&gt;, and the part that matters here is &lt;code&gt;projects/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;/Users/alex/work/dashboard&lt;/code&gt; becomes &lt;code&gt;-Users-alex-work-dashboard&lt;/code&gt;. When you run &lt;code&gt;claude --resume&lt;/code&gt;, Claude Code encodes whatever directory you're standing in and goes looking for a folder with that name. Each &lt;code&gt;.jsonl&lt;/code&gt; inside is a single session, one JSON object per line, appended as you talk.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things break when you copy it
&lt;/h2&gt;

&lt;p&gt;The folder name is the obvious one. Your username is different on the new machine, or the project lives somewhere else entirely, and now &lt;code&gt;-Users-alex-work-dashboard&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Rename the folder and you hit the second problem, which is that every line of the transcript carries the old path inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/Users/alex/work/dashboard"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;code&gt;.claude.json&lt;/code&gt;, which keeps its own &lt;code&gt;projects&lt;/code&gt; 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.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Fixing it by hand
&lt;/h2&gt;

&lt;p&gt;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 &lt;code&gt;/Users/alex/work/dashboard&lt;/code&gt; on a Mac to &lt;code&gt;/home/sam/dev/dashboard&lt;/code&gt; on a Linux box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1. copy the project folder, renamed for the new path&lt;/span&gt;
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="s2"&gt;"~/.claude/projects/-Users-alex-work-dashboard"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="s2"&gt;"~/.claude/projects/-home-sam-dev-dashboard"&lt;/span&gt;

&lt;span class="c"&gt;# 2. rewrite the cwd recorded inside every transcript&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.claude/projects/-home-sam-dev-dashboard
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="s1"&gt;'s#/Users/alex/work/dashboard#/home/sam/dev/dashboard#g'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;.jsonl

&lt;span class="c"&gt;# 3. re-key the project entry in .claude.json&lt;/span&gt;
&lt;span class="c"&gt;#    (open it and rename the key under "projects")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;cd&lt;/code&gt; into the project on the new machine, run &lt;code&gt;claude --resume&lt;/code&gt;, 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why that sed line is a trap
&lt;/h2&gt;

&lt;p&gt;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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;on disk:            "cwd":"C:\\Users\\alex\\work\\dashboard"
what you searched:  C:\Users\alex\work\dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;After that come the smaller ones, and there are more than you'd expect. Paths turn up inside message bodies as well as in &lt;code&gt;cwd&lt;/code&gt;. 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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex and opencode store it differently again
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;Codex&lt;/strong&gt; writes a "rollout" per conversation to &lt;code&gt;$CODEX_HOME/sessions/YYYY/MM/DD/rollout-&amp;lt;timestamp&amp;gt;-&amp;lt;uuid&amp;gt;.jsonl&lt;/code&gt;, with &lt;code&gt;$CODEX_HOME&lt;/code&gt; defaulting to &lt;code&gt;~/.codex&lt;/code&gt;. The project path lives in a &lt;code&gt;cwd&lt;/code&gt; 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 &lt;code&gt;grep&lt;/code&gt; and &lt;code&gt;sed&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;One more thing worth knowing if you go digging: the same conversation is recorded twice in each rollout. &lt;code&gt;response_item&lt;/code&gt; lines are the model-facing history and &lt;code&gt;event_msg&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;opencode&lt;/strong&gt; skips files altogether and keeps sessions in SQLite, normally at &lt;code&gt;~/.local/share/opencode/opencode.db&lt;/code&gt;, with the project path in a &lt;code&gt;directory&lt;/code&gt; column on the sessions table. That makes the text-editing approach useless, since there's nothing to &lt;code&gt;sed&lt;/code&gt;, 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:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;opencode&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="nv"&gt;"UPDATE session SET directory = '/home/sam/dev/dashboard'
             WHERE directory = '/Users/alex/work/dashboard'"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Same conceptual problem three times over, with three unrelated storage shapes to learn and three different ways to get it wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool I built for this
&lt;/h2&gt;

&lt;p&gt;I got tired of doing the manual version and wrote &lt;a href="https://github.com/gowtham-sai-yadav/entangle" rel="noopener noreferrer"&gt;entangle&lt;/a&gt;, a single Go binary that handles the rewriting and moves sessions between machines. Handing one to a teammate goes over &lt;a href="https://magic-wormhole.readthedocs.io/" rel="noopener noreferrer"&gt;magic-wormhole&lt;/a&gt;, so what you exchange is three words rather than a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;you:   entangle send &amp;lt;session-id&amp;gt;
       -&amp;gt; 4-october-stormy

them:  entangle receive 4-october-stormy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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, &lt;code&gt;entangle export&lt;/code&gt; and &lt;code&gt;entangle import&lt;/code&gt; do the same rewriting in bulk. It reads Claude Code, Codex and opencode.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Source is at &lt;a href="https://github.com/gowtham-sai-yadav/entangle" rel="noopener noreferrer"&gt;github.com/gowtham-sai-yadav/entangle&lt;/a&gt;, 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.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>cli</category>
      <category>go</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
