---
title: How Claude Code, Cursor, Copilot, and Trae Store Chats on Disk
description: Compare Claude Code, Cursor, Copilot, and Trae storage paths. Distinguish raw copies from exports, and verify migration retention with test manifests.
image: "https://zengliangyi.com/og/blog/en/ai-conversation-management-options.png"
---

A conversation-management approach is useful when it retains what you need, makes it retrievable, and supports checking results after a tool change. The original ranked four tools for portability and search. Without current versions and a common sample, those rankings are not evidence. This article keeps the four-way comparison while using pinned adapter inputs and executable acceptance checks.

## Specify the layer being compared

Raw copies preserve upstream storage evidence. Readable exports support inspection. Normalized conversations support cross-source lookup. A summary knowledge base supports reuse, with information changes from selection and generation. These layers can coexist; a summary cannot replace a raw backup.

Record the tool version, OS, source, read or export method, and date. History browsing, session resumption, content search, cross-project search, and semantic retrieval are distinct capabilities. The [Claude Code workflows](https://code.claude.com/docs/en/common-workflows) document session resumption and its picker; [VS Code session documentation](https://code.visualstudio.com/docs/agents/run/sessions/manage-sessions) documents JSON export. A documented export does not establish compatibility with this project’s adapter.

## Four entry points impose different retention work

This comparison concerns shapes readable by the pinned ChatCrystal implementation, rather than every current UI, cloud-storage mode, or performance property of these tools.

| Source | Pinned adapter entry point | Evidence needed during migration |

| --- | --- | --- |

| Claude Code | Project JSONL in  Claude adapter | The copy can be reread; block, tool, and thought selection is recorded |

| Cursor | Workspace index and global bubbles in  Cursor adapter | Both samples belong to the intended scope; orphans and version changes are explainable |

| Copilot | Workspace/empty-window locations, first JSONL snapshot or legacy JSON in  Copilot adapter | Later patches are not mistaken for a latest full snapshot; timestamp and response retention is evidenced |

| Trae | SQLite KV and nested task content in  Trae adapter | sessionId lookup, content precedence, and synthetic time are not mistaken for original timestamps |

JSONL can be inspected with text tools, while versions and record meaning still matter. SQLite supports queries, while consistent copies, actual tables and keys, and JSON decoding still matter. Either container may hold sensitive project material; establish retention and sharing scope for your migration samples. These are data-handling requirements rather than format or ecosystem rankings.

## Apply the same acceptance manifest to four candidates

Label source, conversation ID, project, message order, content, tool flags, and thoughts in controlled samples, then compare migrated output. Counting two messages can miss source-ID collisions, mismatched content, or missing thoughts. Add original timestamps, parent references, attachments, and full tool traces to real acceptance separately.

The program constructs the same **teaching manifest** for four sources. It does not read their storage or run their clients or ChatCrystal, and it produces no actual product ranking. Save lesson.mjs and run Node 24+, without npm dependencies.



```js
import   assert   from   'node:assert/strict'  ;  
  const   sources   =   [  'claude-code'  ,   'cursor'  ,   'copilot'  ,   'trae'  ];  
  const   expected   =   sources.  map  ((  source  )   =>   ({  
    source,  
    id:   'shared-7'  ,  
    project:   'fixture-project'  ,  
    messages: [  
      { id:   'u1'  , role:   'user'  , content:   'fixture question'  , thinking:   null  , hasToolUse:   false   },  
      {  
        id:   'a1'  ,  
        role:   'assistant'  ,  
        content:   'fixture answer'  ,  
        thinking:   'fixture thought'  ,  
        hasToolUse:   true  ,  
      },  
    ],  
  }));  
  function   audit  (  actual  ) {  
    const   key   =   (  item  )   =>   item.source   +   ':'   +   item.id;  
    const   found   =   new   Map  ();  
    const   issues   =   [];  
    for   (  const   item   of   actual) {  
      const   id   =   key  (item);  
      if   (found.  has  (id)) issues.  push  (id   +   ':duplicate'  );  
      found.  set  (id, item);  
    }  
    for   (  const   reference   of   expected) {  
      const   id   =   key  (reference);  
      const   item   =   found.  get  (id);  
      if   (  !  item) {  
        issues.  push  (id   +   ':missing'  );  
        continue  ;  
      }  
      if   (item.project   !==   reference.project) issues.  push  (id   +   ':project'  );  
      if   (item.messages.  length   !==   reference.messages.  length  ) issues.  push  (id   +   ':message-count'  );  
      reference.messages.  forEach  ((  message  ,   index  )   =>   {  
        for   (  const   field   of   [  'id'  ,   'role'  ,   'content'  ,   'thinking'  ,   'hasToolUse'  ]) {  
          if   (item.messages[index]?.[field]   !==   message[field])  
            issues.  push  (id   +   ':'   +   index   +   ':'   +   field);  
        }  
      });  
      found.  delete  (id);  
    }  
    for   (  const   id   of   found.  keys  ()) issues.  push  (id   +   ':unexpected'  );  
    return   issues;  
  }  
  const   intact   =   structuredClone  (expected);  
  assert.  deepEqual  (  audit  (intact), []);  
  const   lossy   =   structuredClone  (expected);  
  lossy[  0  ].messages[  1  ].thinking   =   null  ;  
  assert.  ok  (  audit  (lossy).  includes  (  'claude-code:shared-7:1:thinking'  ));  
  const   missing   =   expected.  filter  ((  item  )   =>   item.source   !==   'cursor'  );  
  assert.  ok  (  audit  (missing).  includes  (  'cursor:shared-7:missing'  ));  
  const   reordered   =   structuredClone  (expected);  
  reordered[  2  ].messages.  reverse  ();  
  assert.  ok  (  audit  (reordered).  includes  (  'copilot:shared-7:0:role'  ));  
  assert.  ok  (  
    audit  ([  ...  intact,   structuredClone  (intact[  0  ])]).  includes  (  'claude-code:shared-7:duplicate'  )  
  );  
  console.  log  (  'four-source fixture acceptance passes; no product runtime is compared'  );
```



Intact manifests pass. Missing thinking, an omitted Cursor conversation, reversed Copilot messages, and a repeated same-source ID produce specific findings. A shared id does not merge different sources because the key includes source.

Remove thinking from the field loop and the thought-loss assertion fails. Use only id as the matching key and the intact-manifest assertion fails. Restore each to pass. This verifies selected-field acceptance, not complete real exports or resolution of every cross-source ID collision in ChatCrystal’s local import.

## Choose a combination for the task

For debugging evidence, obtain a verifiable raw copy and document its consistency conditions. For human review, add a readable export. For cross-source lookup, check normalized identity and fields. For reusable lessons, then verify summaries, citations, and retrieval hits. Combine these steps for the purpose while retaining a path to the source.

ChatCrystal normalizes selected output through [SourceAdapter](https://github.com/ZengLiangYi/ChatCrystal/blob/764276814e5c5a33ecbfeae63754f74b9261a3a8/server/src/parser/adapter.ts); summaries, embeddings, and search have separate failure boundaries. This does not establish lossless upstream conversion. Unknown versions, missing sources, and empty results need investigation rather than a single import-success label.

A comparison should leave expected samples, actual retention and omissions, failure cases, and recovery steps. Define search tasks and expected results before measuring local latency and relevance. This article reports no actual speed, quality, cost, or product ranking. Continue with [storage lookup](/en/blog/ai-conversation-format-divergence/) and [import identity and hashing](/en/blog/source-adapter-internals/) to apply the manifest to specific paths.

Share:

##  Related Posts

[##  AI Conversation Formats: JSONL Events vs SQLite Snapshots

Compare 5 AI adapter formats: separate JSONL events from SQLite keys, and inspect indexed, orphan, and unknown-version chat transcripts in memory stores.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#Node.js

#SQLite](/en/blog/ai-conversation-format-divergence)    [##  sql.js WASM Persistence: Memory, Exports & Nested Savepoints

Trace ChatCrystal’s database lifecycle with sql.js 1.14.2 fixtures, separating SQL commits, exports, file writes, foreign-key restoration, and nested rollback.          曾亮懿       October 7, 2026      5 min read

#ChatCrystal

#Node.js

#SQLite](/en/blog/sqljs-wasm-persistence)    [##  Vectra Vector Search: Metadata, Updates, and Local Cache

Inspect Vectra 0.15.0 query and storage paths with temporary indexes, observing external metadata, files left after cancellation, and instance-cache boundaries.          曾亮懿       October 7, 2026      4 min read

#ChatCrystal

#Node.js

#SQLite](/en/blog/vectra-search-internals)

##  From writing to code

Explore ChatCrystal, developer tools and daily learning notes on my GitHub. Questions about the implementation are welcome.

[Visit my GitHub](https://github.com/ZengLiangYi)

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "ZengLiangYi Blog",
  "alternateName": [
    "Zeng Liangyi",
    "ZengLiangYi",
    "zengliangyi.com"
  ],
  "url": "https://zengliangyi.com",
  "description": "曾亮懿的个人网站。记录 AI Native 探索、开发者工具与全栈产品实践。"
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "How Claude Code, Cursor, Copilot, and Trae Store Chats on Disk",
  "description": "Compare Claude Code, Cursor, Copilot, and Trae storage paths. Distinguish raw copies from exports, and verify migration retention with test manifests.",
  "url": "https://zengliangyi.com/en/blog/ai-conversation-management-options/",
  "image": "https://zengliangyi.com/images/blog/ai-conversation-management-options.svg",
  "datePublished": "2026-10-07T00:00:00.000Z",
  "dateModified": "2026-10-23T00:00:00.000Z",
  "author": {
    "@type": "Person",
    "name": "曾亮懿",
    "url": "https://zengliangyi.com/en/about"
  },
  "publisher": {
    "@type": "Organization",
    "name": "ZengLiangYi Blog",
    "logo": {
      "@type": "ImageObject",
      "url": "https://zengliangyi.com/favicon-96x96.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://zengliangyi.com/en/blog/ai-conversation-management-options/"
  }
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://zengliangyi.com/en"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog",
      "item": "https://zengliangyi.com/en/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "How Claude Code, Cursor, Copilot, and Trae Store Chats on Disk",
      "item": "https://zengliangyi.com/en/blog/ai-conversation-management-options/"
    }
  ]
}
```
