Summary
/desktop reports Session transferred to Claude Desktop to the user, but the session never appears in Claude Desktop. Two distinct bugs are stacked:
- URL scheme misregistered against the wrong bundle ID (intermittent, environment-dependent on macOS).
- Session ID always resolves to
null on the Desktop side, regardless of whether the URL was delivered correctly (architectural — appears to affect all CLI-to-Desktop handoffs).
Result: even when (1) doesn't apply, (2) silently breaks the handoff, and the CLI's success message is misleading.
Environment
- macOS (Apple Silicon)
- Claude Code CLI: 2.1.126
- Claude Desktop: 1.5354.0
- Claude Desktop's embedded Claude Code SDK (in VM): 2.1.121
Bug 1 — `claude://` URL scheme bound to helper subprocess
`~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist` had:
```
LSHandlerRoleAll = "com.anthropic.claudefordesktop.helper"
LSHandlerURLScheme = "claude"
```
The `.helper` bundle ID belongs to the sandboxed Electron helper subprocesses (`Claude Helper.app`, `Claude Helper (Renderer).app`, etc.) that don't handle URL events. The main app's bundle ID is `com.anthropic.claudefordesktop` (no `.helper`).
Result: `open "claude://anything"` returns `_LSOpenURLsWithCompletionHandler() failed with error -600 for the URL claude://` (procNotFound). The CLI nevertheless prints `Session transferred to Claude Desktop`.
`lsregister -dump` confirms only the main bundle claims the scheme:
```
path: /Applications/Claude.app
identifier: com.anthropic.claudefordesktop
claimed schemes: claude:
```
So nothing in the static registration explains why the user pref points at the helper. Suspected cause: somewhere during install or runtime, Claude.app calls a setDefaultProtocolClient-equivalent and passes the helper's bundle ID. Could not confirm from outside the app.
Workaround: patch the plist to point at the main bundle:
```bash
python3 - <<'PYEOF'
import plistlib, pathlib, os
p = pathlib.Path(os.path.expanduser("~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"))
d = plistlib.loads(p.read_bytes())
for h in d.get("LSHandlers", []):
if h.get("LSHandlerURLScheme") == "claude":
h["LSHandlerRoleAll"] = "com.anthropic.claudefordesktop"
p.write_bytes(plistlib.dumps(d))
PYEOF
killall cfprefsd
```
After that, `open "claude://test"` no longer returns -600 and Claude.app launches correctly.
Bug 2 — `sessionId` always resolves to `null` on the Desktop side
Even when (1) is resolved and the URL is delivered, Claude Desktop's main log shows:
```
[CCD] LocalSessions.setFocusedSession: sessionId=null
```
…on every single `/desktop` invocation. Across multiple `/desktop` calls in different sessions, only one `setFocusedSession` event in the entire log resolved to a real session ID; every other entry (290+ events) is `null`.
Suspected cause: CLI sessions are stored at `/.claude/projects///`, but Claude Desktop's Claude Code runs in an Apple Virtualization Framework VM with its own SDK install at `/Library/Application Support/Claude/claude-code-vm//`. The VM-side Claude Code does not appear to mount or proxy the host's `~/.claude/projects/` directory, so when it tries to load the session ID emitted by the host CLI it can't find the session file → `null`.
This is the more important bug — even if Bug 1 is fixed, Bug 2 silently breaks the handoff.
Reproduce
- Start an interactive Claude Code session in terminal.
- Run `/desktop`.
- Observe CLI prints `Session transferred to Claude Desktop`.
- `tail -f ~/Library/Logs/Claude/main.log | grep setFocusedSession` while running step 2.
- Observe `sessionId=null`.
- Claude Desktop's window does not surface the transferred session.
Suggested fixes
- Don't print `Session transferred to Claude Desktop` until Desktop has acknowledged a non-null session ID. Alternatively: `Failed to transfer session — see ~/Library/Logs/Claude/main.log` when `setFocusedSession` returns null.
- Verify Desktop's URL handler registers under the main bundle ID, not `.helper`.
- Either share the host's `~/.claude/projects/` into the VM, or have `/desktop` upload/import the session into the VM's storage before firing the deeplink.
Additional logs
`detectedProjects` errors (`sqlite3 via disclaimer exited with code 14`) are common in main.log but appear unrelated — they're from Desktop's project-discovery scanning of VS Code/Cursor/Zed databases, not from `/desktop` itself.
Summary
/desktopreportsSession transferred to Claude Desktopto the user, but the session never appears in Claude Desktop. Two distinct bugs are stacked:nullon the Desktop side, regardless of whether the URL was delivered correctly (architectural — appears to affect all CLI-to-Desktop handoffs).Result: even when (1) doesn't apply, (2) silently breaks the handoff, and the CLI's success message is misleading.
Environment
Bug 1 — `claude://` URL scheme bound to helper subprocess
`~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist` had:
```
LSHandlerRoleAll = "com.anthropic.claudefordesktop.helper"
LSHandlerURLScheme = "claude"
```
The `.helper` bundle ID belongs to the sandboxed Electron helper subprocesses (`Claude Helper.app`, `Claude Helper (Renderer).app`, etc.) that don't handle URL events. The main app's bundle ID is `com.anthropic.claudefordesktop` (no `.helper`).
Result: `open "claude://anything"` returns `_LSOpenURLsWithCompletionHandler() failed with error -600 for the URL claude://` (procNotFound). The CLI nevertheless prints `Session transferred to Claude Desktop`.
`lsregister -dump` confirms only the main bundle claims the scheme:
```
path: /Applications/Claude.app
identifier: com.anthropic.claudefordesktop
claimed schemes: claude:
```
So nothing in the static registration explains why the user pref points at the helper. Suspected cause: somewhere during install or runtime, Claude.app calls a setDefaultProtocolClient-equivalent and passes the helper's bundle ID. Could not confirm from outside the app.
Workaround: patch the plist to point at the main bundle:
```bash
python3 - <<'PYEOF'
import plistlib, pathlib, os
p = pathlib.Path(os.path.expanduser("~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"))
d = plistlib.loads(p.read_bytes())
for h in d.get("LSHandlers", []):
if h.get("LSHandlerURLScheme") == "claude":
h["LSHandlerRoleAll"] = "com.anthropic.claudefordesktop"
p.write_bytes(plistlib.dumps(d))
PYEOF
killall cfprefsd
```
After that, `open "claude://test"` no longer returns -600 and Claude.app launches correctly.
Bug 2 — `sessionId` always resolves to `null` on the Desktop side
Even when (1) is resolved and the URL is delivered, Claude Desktop's main log shows:
```
[CCD] LocalSessions.setFocusedSession: sessionId=null
```
…on every single `/desktop` invocation. Across multiple `/desktop` calls in different sessions, only one `setFocusedSession` event in the entire log resolved to a real session ID; every other entry (290+ events) is `null`.
Suspected cause: CLI sessions are stored at `
/.claude/projects///`, but Claude Desktop's Claude Code runs in an Apple Virtualization Framework VM with its own SDK install at `/Library/Application Support/Claude/claude-code-vm//`. The VM-side Claude Code does not appear to mount or proxy the host's `~/.claude/projects/` directory, so when it tries to load the session ID emitted by the host CLI it can't find the session file → `null`.This is the more important bug — even if Bug 1 is fixed, Bug 2 silently breaks the handoff.
Reproduce
Suggested fixes
Additional logs
`detectedProjects` errors (`sqlite3 via disclaimer exited with code 14`) are common in main.log but appear unrelated — they're from Desktop's project-discovery scanning of VS Code/Cursor/Zed databases, not from `/desktop` itself.