[BUG] claude://resume duplicates a session already open as a native Desktop tab; no deep link to focus an existing native session
Summary
claude://resume?session=<cli-session-id> imports a CLI session by minting a new Desktop session whose id is hardcoded to local_<cliSessionId>, and de-duplicates purely on that id key. As a consequence:
- If the same CLI session is already open as a native Desktop Code tab (created inside Desktop, not via an earlier
resumeimport), the deep link cannot find it and creates a duplicate tab — a snapshot fork of identical content that no longer stays in sync with the live native session. - There is no deep link (or any documented URL) to focus an existing native Desktop session by id, so external integrations can only ever activate the app, never navigate to a specific native tab.
This is adjacent to #79309 (Desktop "Open in terminal" also spawns a new session) but on the inbound external→Desktop path.
Environment
- Claude Desktop
1.24012.1(macOS) claude://URL scheme,resumeroute
Steps to reproduce
- In Claude Desktop, open a Code session natively (New → Code). Note its content.
- Find that session's CLI session id (the UUID in the transcript filename under
~/.claude/projects/<project>/<uuid>.jsonl, which Desktop stores ascliSessionIdin~/Library/Application Support/Claude/claude-code-sessions/<account>/<org>/local_*.json). - Trigger
open "claude://resume?session=<uuid>".
Expected: the existing native tab is focused.
Actual: a second "General coding session" tab appears with duplicated content; the original native tab is untouched.
Root cause (from the shipped bundle)
importCliSession de-dupes on a key convention, not on the cliSessionId property:
const r = `${LOCAL_SESSION_PREFIX}${e}`; // "local_" + cliSessionId
if (this.sessions.get(r)) { // key lookup only
this.unarchiveSession(r);
return r; // → focuses the tab
}
// else: read transcript, create a new session whose id is set to r
- A session created by
resumegetsid = local_<cliSessionId>, so a secondresumewith the same id hits the key and focuses — this is why re-importing works. - A native Desktop session gets
id = local_<random-uuid>; itscliSessionIdlives only as a stored property. The keylocal_<cliSessionId>is never present, so the lookup always misses and duplicates.
The de-dup data (cliSessionId) is already persisted on every session record — the lookup simply doesn't consult it.
Suggested fix (either one)
- In
importCliSession, before creating a new session, also look up any existing session (native included) whose storedcliSessionId === e; if found, focus it instead of importing. - Or expose a dedicated focus route, e.g.
claude://claude-code-desktop/focus?cliSessionId=<uuid>, that navigates to an existing native session without importing.
Either would let external tools (companion apps, launchers, hardware remotes) deep-link users to the actual running session rather than a stale snapshot copy.
Context
Building a companion app that surfaces active Claude Code sessions and offers "open this session in Desktop". resume works great for headless / CLI-only sessions (gives them a UI), and re-focusing an already-imported session works — but native Desktop sessions can only be app-activated, and blindly calling resume on them silently duplicates. A focus-by-cliSessionId path would close the gap.
Showing cached comments. Read the full discussion on GitHub ↗
3 Comments
This renders
claude://resumeuseless for many use cases, or at least undermines the convenience it was created to bring. Anthropic did add a deep-linking capability but only left it at a skeleton stage. Even if you import, what use is the "General coding session" title (#83051)? And I don't think a separate "focus" route is required. Focusing should work out of the box.This is a duplicate session-identity case rather than missing transcript data. The native Desktop tab keeps
cliSessionIdas a stored property under a different local session key, whileclaude://resumelooks forlocal_<cliSessionId>, so the lookup misses and imports a second snapshot.I built
BasedGPT/claude-code-session-recoveryfor this duplicate-metadata family. From the affected macOS machine, runpython tools/diagnose.pyfirst and keep the output. It should identify whether two session records share the samecliSessionIdand print the exact safe repair command. Do not apply a write-bearing command blindly.Preserve the native session record and do not delete either tab or its metadata before the inventory is complete. The
cleanup_synth_duplicates.pyroute is the relevant boundary only when the diagnosis confirms duplicate metadata sharing acliSessionId; it is dry-run by default and should be applied only after Desktop is fully closed.Hope this helps, if my tools are able to help you, would appreciate a ⭐ :)
Note that this duplication also kills the session's worktree if it was in one: once you open a worktree-attached session, it gets moved from the worktree folder under
~/.claude/projectsto the main one. The real session then continues to leave traces of its prior existence in the original file. I just found one of my sessions duplicated across folders, and the original one had only this in its .jsonl: