<?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: 李成龙</title>
    <description>The latest articles on DEV Community by 李成龙 (@chenglongli).</description>
    <link>https://dev.to/chenglongli</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%2F3995315%2Fc3927ee8-fa2f-4c5a-9709-74c132517c49.jpg</url>
      <title>DEV Community: 李成龙</title>
      <link>https://dev.to/chenglongli</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chenglongli"/>
    <language>en</language>
    <item>
      <title>Why Copying .codex Isn't Enough: Building a Cross-Platform Codex Session Migrator</title>
      <dc:creator>李成龙</dc:creator>
      <pubDate>Sun, 21 Jun 2026 13:02:10 +0000</pubDate>
      <link>https://dev.to/chenglongli/why-copying-codex-isnt-enough-building-a-cross-platform-codex-session-migrator-4k3h</link>
      <guid>https://dev.to/chenglongli/why-copying-codex-isnt-enough-building-a-cross-platform-codex-session-migrator-4k3h</guid>
      <description>&lt;p&gt;When I moved Codex work between computers, I expected the migration process to be simple: copy the old &lt;code&gt;.codex&lt;/code&gt; directory to the new machine and reopen Codex.&lt;/p&gt;

&lt;p&gt;The files were there, but the result was not what I expected. Historical projects did not reliably appear, sessions could be missing from the interface, and paths stored on macOS made no sense on Windows.&lt;/p&gt;

&lt;p&gt;That problem led me to build &lt;strong&gt;Codex Migrate&lt;/strong&gt;, an open-source Rust desktop application and CLI for migrating, repairing, backing up, and exporting local Codex sessions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyzqmd6faqt5vdewtc65.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyyzqmd6faqt5vdewtc65.png" alt="Codex Migrate application overview" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is less about announcing the project and more about the engineering problems behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first incorrect assumption: files are the whole state
&lt;/h2&gt;

&lt;p&gt;A Codex session is primarily represented by rollout JSONL data, but simply placing those files somewhere under the new &lt;code&gt;.codex&lt;/code&gt; directory does not guarantee that the application will present them correctly.&lt;/p&gt;

&lt;p&gt;There are several pieces of state involved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rollout JSONL files containing the conversation history&lt;/li&gt;
&lt;li&gt;Thread metadata such as title, timestamps, archive state, and current working directory&lt;/li&gt;
&lt;li&gt;SQLite indexes used by the local application&lt;/li&gt;
&lt;li&gt;Paths that were valid on the source computer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates an important distinction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Preserving the conversation data is not the same as restoring a usable project and session index.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the migration tool, I therefore treat rollout JSONL as the source of truth, while SQLite is an adapter used to restore the metadata needed by the target environment. The source database is never copied wholesale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paths are part of the problem
&lt;/h2&gt;

&lt;p&gt;Consider a session created under:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/Users/alex/Projects/my-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;After moving to Windows, the corresponding project might be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;D:\Projects\my-app&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The old path is not merely cosmetic. It can affect how a session is grouped, displayed, and reopened. A migration tool must understand several path families:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;POSIX paths&lt;/li&gt;
&lt;li&gt;Windows drive-letter paths&lt;/li&gt;
&lt;li&gt;UNC paths&lt;/li&gt;
&lt;li&gt;WSL paths such as &lt;code&gt;/mnt/d/Projects&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex Migrate groups sessions by their original working directory and asks the user to bind each selected project to a real directory on the target computer. It also supports parent-directory mapping. If all projects moved from &lt;code&gt;/Users/alex/Projects&lt;/code&gt; to &lt;code&gt;D:\Projects&lt;/code&gt;, one rule can map the entire tree.&lt;/p&gt;

&lt;p&gt;For sessions whose projects no longer exist, there is a history-only mode. This preserves access to the conversation without pretending that the original working directory still exists.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff35ina97t8mpd0k1bf3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ff35ina97t8mpd0k1bf3t.png" alt="Three-step migration workflow" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A migration tool should not silently invent history
&lt;/h2&gt;

&lt;p&gt;The next issue was conflict handling. A thread UUID may already exist on the target computer, so blindly overwriting files would be unsafe.&lt;/p&gt;

&lt;p&gt;The merge planner uses conservative rules:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source and target state&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;UUID does not exist locally&lt;/td&gt;
&lt;td&gt;Import it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UUID and content hash are identical&lt;/td&gt;
&lt;td&gt;Skip the duplicate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target rollout is a full prefix of source&lt;/td&gt;
&lt;td&gt;Keep the longer source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source rollout is a full prefix of target&lt;/td&gt;
&lt;td&gt;Keep the longer target&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Same UUID has divergent content&lt;/td&gt;
&lt;td&gt;Stop and report a conflict&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last case matters. Two JSONL files with the same UUID may represent histories that diverged after synchronization or manual copying. Automatically concatenating them could create a conversation that never actually happened. Generating a new UUID would hide the identity conflict rather than resolve it.&lt;/p&gt;

&lt;p&gt;The first version therefore refuses to splice divergent histories.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transactions matter even for a local desktop utility
&lt;/h2&gt;

&lt;p&gt;Session migration modifies user data, so a partially completed import is unacceptable.&lt;/p&gt;

&lt;p&gt;The import flow is transactional at the application level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check whether the target database appears to be in use.&lt;/li&gt;
&lt;li&gt;Create a SQLite Online Backup snapshot.&lt;/li&gt;
&lt;li&gt;Validate the selected rollout files.&lt;/li&gt;
&lt;li&gt;Write files into a staging area.&lt;/li&gt;
&lt;li&gt;Move them into their final locations.&lt;/li&gt;
&lt;li&gt;Update only schema fields detected at runtime.&lt;/li&gt;
&lt;li&gt;Verify rollout and database consistency.&lt;/li&gt;
&lt;li&gt;Restore the snapshot and remove new files if a step fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rollback snapshots are stored under:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$CODEX_HOME/migration_transactions/&amp;lt;TRANSACTION_ID&amp;gt;/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The GUI also lets users inspect, restore, and delete old snapshots.&lt;/p&gt;

&lt;p&gt;This was more work than a direct file copy, but migration software needs a clear failure model. "Most files were copied" is not a valid success state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Supporting existing synchronized .codex directories
&lt;/h2&gt;

&lt;p&gt;Not every user performs an explicit migration. Some people replace the new machine's &lt;code&gt;.codex&lt;/code&gt; directory with the old one. Others synchronize it between two computers.&lt;/p&gt;

&lt;p&gt;For those cases, copying sessions is unnecessary—the data already exists. What is broken is the current path metadata.&lt;/p&gt;

&lt;p&gt;That led to a separate &lt;strong&gt;path repair&lt;/strong&gt; workflow. It scans the local Codex environment, displays projects with invalid paths by default, and lets the user:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Map projects individually&lt;/li&gt;
&lt;li&gt;Apply a parent-directory mapping&lt;/li&gt;
&lt;li&gt;Include all projects when reviewing mappings&lt;/li&gt;
&lt;li&gt;Preview changes before updating local metadata&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keeping path repair separate from import made the model easier to reason about: one operation moves session data, while the other repairs references to data already present.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exporting a conversation is more complicated than printing JSON
&lt;/h2&gt;

&lt;p&gt;I also wanted sessions to remain readable outside Codex. The first HTML exporter exposed another class of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A rollout can contain multiple representations of the same assistant response&lt;/li&gt;
&lt;li&gt;Tool events should not appear as duplicated chat messages&lt;/li&gt;
&lt;li&gt;User and assistant messages need different layouts&lt;/li&gt;
&lt;li&gt;Images and tool screenshots may be referenced through local paths or structured payloads&lt;/li&gt;
&lt;li&gt;A portable export should not depend on a separate asset folder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The exporter now produces one self-contained HTML file per selected session. User messages appear on the right, while Codex responses use the wider left-side layout. Images and tool screenshots are embedded directly as data URLs.&lt;/p&gt;

&lt;p&gt;This makes the export larger, but the portability tradeoff is worth it: one conversation equals one file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross-platform GUI details still matter
&lt;/h2&gt;

&lt;p&gt;The core is written in Rust, with &lt;code&gt;egui/eframe&lt;/code&gt; for the native GUI. Cross-platform behavior was not limited to filesystem logic.&lt;/p&gt;

&lt;p&gt;I encountered platform-specific differences in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Font baseline alignment&lt;/li&gt;
&lt;li&gt;Application icon embedding on Windows&lt;/li&gt;
&lt;li&gt;macOS bundle signing&lt;/li&gt;
&lt;li&gt;Native folder selection&lt;/li&gt;
&lt;li&gt;Path separators during validation&lt;/li&gt;
&lt;li&gt;GUI versus console subsystem behavior on Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One example: the initial macOS release contained a linker-generated ad-hoc signature on the executable but no complete bundle signature after resources were added. Gatekeeper reported the downloaded application as damaged. The fix was to sign the completed app bundle and verify it with &lt;code&gt;codesign --verify --deep --strict&lt;/code&gt; before packaging.&lt;/p&gt;

&lt;p&gt;It is a useful reminder that a successful compilation does not mean a distributable desktop application is correctly packaged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deliberate data boundaries
&lt;/h2&gt;

&lt;p&gt;The tool intentionally migrates only session-related data. It excludes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication&lt;/li&gt;
&lt;li&gt;Configuration&lt;/li&gt;
&lt;li&gt;Skills&lt;/li&gt;
&lt;li&gt;Plugins&lt;/li&gt;
&lt;li&gt;Logs and caches&lt;/li&gt;
&lt;li&gt;Device-specific state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This keeps the operation understandable and avoids moving credentials between computers. It also makes backups easier to inspect: the minimal backup uses the same basic structure as a Codex directory rather than a proprietary archive format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current state and limitations
&lt;/h2&gt;

&lt;p&gt;Codex Migrate currently provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A native GUI in English and Chinese&lt;/li&gt;
&lt;li&gt;A CLI using the same migration engine&lt;/li&gt;
&lt;li&gt;macOS, Windows, Linux, and WSL path handling&lt;/li&gt;
&lt;li&gt;Active and archived session support&lt;/li&gt;
&lt;li&gt;Selective project and session import&lt;/li&gt;
&lt;li&gt;Path repair and parent-directory mapping&lt;/li&gt;
&lt;li&gt;Conflict-aware merging&lt;/li&gt;
&lt;li&gt;Transaction backups and rollback&lt;/li&gt;
&lt;li&gt;Self-contained HTML export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main limitation is compatibility risk. Local Codex storage structures can change between versions, so the project performs runtime schema inspection instead of assuming one permanent SQLite layout. Even so, real-world reports remain important.&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;The project is available on GitHub under the MIT license:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/ChenglongLi777/codex-migrate" rel="noopener noreferrer"&gt;https://github.com/ChenglongLi777/codex-migrate&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prebuilt releases are available for macOS and Windows. The binaries are not currently signed with commercial certificates or Apple-notarized, so the repository documents the relevant first-launch warnings and publishes SHA-256 checksums.&lt;/p&gt;

&lt;p&gt;Codex Migrate is an independent community project and is not affiliated with or endorsed by OpenAI.&lt;/p&gt;

&lt;p&gt;If you have moved Codex history between operating systems—or regularly synchronize a &lt;code&gt;.codex&lt;/code&gt; directory—I would be interested in the edge cases you encountered.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>opensource</category>
      <category>devtools</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
