[FEATURE] Let the desktop app surface on-disk transcripts (cross-machine session continuity)
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
Summary:
The Claude Code CLI resumes conversations by scanning ~/.claude/projects/<project-key>/*.jsonl. The desktop app does not — it discovers conversations through a per-installation Electron IndexedDB store in ~/Library/Application Support/Claude/. As a result, transcripts that are present, valid, and readable on disk are invisible to the desktop app, and there is no supported way to continue a conversation on a second machine using the app.
Request: allow the desktop app to discover conversations from ~/.claude/projects/<project-key>/ — either by scanning, or by offering an explicit "import/rescan" action — so that two machines pointed at the same project path can resume the same conversations.
Observed behavior:
The desktop app on the Air lists only its own 4 conversations. The 4 copied ones never appear — not after a full quit and relaunch, and not via scrolling or search.
The CLI on the same machine, in the same directory, lists all 8.
What was verified:
On the Air, after the copy:
All 8 top-level .jsonl files present, owned by jasonshank, mode 600, all readable=yes
Every line of all 27 files (including nested <session-uuid>/subagents/*.jsonl) parses as valid JSON
All transcripts report cwd: /Volumes/Media/Claude, userType: external, gitBranch: HEAD
Record types structurally equivalent between copied and native transcripts (queue-operation, user, assistant, system, last-prompt, attachment, mode, ai-title/custom-title)
~/.claude.json → projects["/Volumes/Media/Claude"] contains no conversation or history list — only allowedTools, mcpContextUris, enabledMcpjsonServers, disabledMcpjsonServers, hasTrustDialogAccepted, projectOnboardingSeenCount, hasClaudeMdExternalIncludesApproved, hasClaudeMdExternalIncludesWarningShown
No conversation index anywhere in ~/Library/Application Support/Claude apart from IndexedDB/https_claude.ai_0.indexeddb.leveldb
A title-record theory was tested and rejected: two of the non-appearing transcripts do contain ai-title records.
So the data is sound. The gap is purely in how the app discovers conversations.
Documented behavior:
This is consistent with the docs, which state:
"The desktop app, Claude Code on the web, and the VS Code extension each maintain their own session history." — https://code.claude.com/docs/en/sessions.md
The behavior is therefore working as designed. This is a feature request, not a bug report. The request is that the design change.
Proposed Solution
Suggested resolution, roughly in order of preference:
Scan ~/.claude/projects/<project-key>/ and merge results into the app's conversation list, deduplicating against IndexedDB by session ID.
An explicit "Import / Rescan conversations" action for a project — lower risk, keeps discovery opt-in.
Documentation. If transcript portability will remain unsupported, say so explicitly in the desktop app docs. The current wording describes separate histories but doesn't state that on-disk transcripts are undiscoverable by the app — which is what led to a full evening of filesystem debugging on correct, valid data.
Item 3 is worth doing regardless of whether 1 or 2 ever happens.
Current workaround:
Claude Code CLI on both machines, both working from the shared path, with the state directory replicated between them. This works — claude --resume shows all 8 conversations on either machine — but it means not using the desktop app for this workflow.
Alternative Solutions
_No response_
Priority
Medium - Would be very helpful
Feature Category
Other
Use Case Example
Why this matters:
The use case is ordinary: one person, multiple machines in one house, wanting to continue a piece of work after moving from a desk to a couch. Today that requires either abandoning the desktop app for the CLI, or using Claude Code on the web.
Claude Code on the web is the documented cross-machine answer, but it runs in Anthropic's cloud and cannot reach local resources — in this case an SMB-mounted NAS, a Proxmox host reachable only over LAN SSH, and a local WordPress dev environment. For local-infrastructure work it isn't a substitute.
That leaves the CLI as the only option, which means giving up the desktop app for the exact workflow it's otherwise well suited to.
Additional Context
<h2 style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px 0px 16px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-size: 17px; font-weight: 600; line-height: 22px; color: rgb(225, 224, 217); margin-block: 24px 8px; padding-block: 0px; font-family: "Anthropic Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(32, 32, 31); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Environment</h2><div class="overflow-x-auto" style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px 0px 16px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); overflow-x: auto; color: rgb(225, 224, 217); font-family: "Anthropic Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(32, 32, 31); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">
OS | macOS (Darwin 25.5.0), Apple Silicon
-- | --
Desktop app | 1.24012.9
CLI | 2.1.220 (installed via npm i -g @anthropic-ai/claude-code)
Transcript version fields | 2.1.219 / 2.1.220
Machines | Mac Mini (user jshan), MacBook Air (user jasonshank)
</div><h2 style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px 0px 16px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-size: 17px; font-weight: 600; line-height: 22px; color: rgb(225, 224, 217); margin-block: 24px 8px; padding-block: 0px; font-family: "Anthropic Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(32, 32, 31); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Setup</h2><p style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px 0px 16px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); color: rgb(225, 224, 217); font-family: "Anthropic Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(32, 32, 31); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Both machines open the<span> </span><strong style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-weight: 500; font-variation-settings: "opsz" 16, "wght" 560;">same</strong><span> </span>working directory:<span> </span><code node="[object Object]" data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-family: "Anthropic Mono", ui-monospace, monospace; font-feature-settings: "calt" 0, "liga" 0; font-variation-settings: normal; font-size: 12px; font-variant-ligatures: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(255, 255, 255, 0.08); color: rgb(225, 224, 217); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">/Volumes/Media/Claude</code>, an SMB mount of a NAS share, mounted at that identical path on both. The path deliberately contains no username, because Claude Code derives the state-folder key from the absolute working-directory path — so both machines produce the same key,<span> </span><code node="[object Object]" data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-family: "Anthropic Mono", ui-monospace, monospace; font-feature-settings: "calt" 0, "liga" 0; font-variation-settings: normal; font-size: 12px; font-variant-ligatures: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(255, 255, 255, 0.08); color: rgb(225, 224, 217); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">-Volumes-Media-Claude</code>.</p><p style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px 0px 16px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); color: rgb(225, 224, 217); font-family: "Anthropic Sans", system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(32, 32, 31); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">Four conversation transcripts originating on the Mini were copied into the Air's<span> </span><code node="[object Object]" data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-family: "Anthropic Mono", ui-monospace, monospace; font-feature-settings: "calt" 0, "liga" 0; font-variation-settings: normal; font-size: 12px; font-variant-ligatures: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(255, 255, 255, 0.08); color: rgb(225, 224, 217); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">~/.claude/projects/-Volumes-Media-Claude/</code>, alongside the Air's own four. The<span> </span><code node="[object Object]" data-epitaxy-inline-code="" style="box-sizing: border-box; border: 0px solid color(srgb 1 1 1 / 0.1); margin: 0px; padding: 0px; outline-color: rgb(85, 152, 231); scrollbar-width: thin; scrollbar-color: rgba(225, 224, 217, 0.35) rgba(0, 0, 0, 0); font-family: "Anthropic Mono", ui-monospace, monospace; font-feature-settings: "calt" 0, "liga" 0; font-variation-settings: normal; font-size: 12px; font-variant-ligatures: none; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(255, 255, 255, 0.08); color: rgb(225, 224, 217); border-radius: 4px; padding-block: 1px; padding-inline: 2px; line-height: 1.4;">"cwd"</code><span> </span>field inside the copied transcripts was rewritten to match the shared path.</p>
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
Ran into exactly this after migrating
~/.claudeto a new machine (Linux, desktop app 1.28929.0, Claude Code 2.1.227): all transcripts intact under~/.claude/projects/, but the desktop session list came up empty.Some findings that may help implementation:
On Linux the desktop's session list is not IndexedDB — it's a plain per-session JSON registry at:
Each file holds
{sessionId, cliSessionId, cwd, title, createdAt, lastActivityAt, isArchived, …}, wherecliSessionIdpoints at~/.claude/projects/<encoded-cwd>/<cliSessionId>.jsonl. The registry is read once at app startup; files added while the app is running are picked up on next launch.Generating one such entry per on-disk transcript fully restores the conversation list. I recovered 30 migrated sessions this way and they open and resume normally in the app — so the gap really is just discovery/registration, not any data or format incompatibility. This also suggests option 1 or 2 from the OP (auto-scan or explicit "Import / Rescan" action, deduplicating by session id) should be cheap to build.
Two things an official import should probably handle (both bit me in practice):
<command-message>/<local-command-caveat>/<ide_opened_file>machinery rather than the user's words; naive "first message as title" fills the list with identical noise titles. Skip to the first real user message, fall back to the slash-command name.For reference,
/desktopdoesn't cover this case either: it moves one live CLI session at a time and is macOS/x64-Windows only.Happy to share the import script if useful.
The CLI transcripts are valid and already on disk, so you can import them into the Desktop session list on the Air today. That is a local registration step rather than native cross-machine sync.
I built
BasedGPT/claude-code-session-recoveryfor this file-layer case. Runpython tools/diagnose.pyon the Mac and follow the exact command it prints. If it finds orphaned transcript files under~/.claude/projects/, review the dry-run metadata entries before applying them, then quit Desktop fully so it reloads the index. The toolkit is Windows-tested; the file formats are the same on macOS, but the commands need the Mac paths from the diagnostic output. Keep a copy of both the Desktop metadata and~/.claude/projects/before any write.That can expose the copied sessions in the Desktop session list, but it does not add continuous sync or the reverse Desktop-to-CLI path.
Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)
Confirming the same root cause on Windows (desktop app doesn't scan
~/.claude/projects/), via a fresh-machine migration rather than a shared-mount setup — copied.jsonltranscripts were invisible to the desktop app despiteclaude --resumefinding and resuming them fine from the terminal.One data point that might be useful for option 2 in the proposal (explicit "import/rescan" action): actively running
claude --resumeinteractively in a terminal, with the desktop app running, does cause that session to surface in the app's sidebar — while the terminal stays open. This suggests there's already a live registration path from an active CLI process to a running desktop app instance (rather than the app needing to scan the filesystem itself), which an explicit rescan feature could potentially piggyback on instead of needing the IndexedDB store rebuilt from scratch. The catch: it's not persistent — closing the terminal auto-archives the session, and you have to manually undo the archive toast to keep it. It also lands under "Other" in the sidebar rather than its project's group (see #89781, same double.claude.jsonentry —\-keyed and/-keyed — pattern observed here too).So today's "workaround" is real but fully manual, one session at a time, with two extra clicks (undo-archive, and eventually fixing the group). Wanted to flag it in case it's a useful shortcut for implementing a proper rescan action rather than a red herring.