Probed 2026-04-26 against Claude Desktop v1.3109.0 (Electron 41.2.0) on Windows 11. Discovery method: live file system exploration + ccl_chromium_reader leveldb reads. See
skills/leveldb-ops/for reusable probe scripts.
Claude Desktop is an Electron app that embeds https://claude.ai as its frontend. It has three tabs — Chat, Cowork, and Code. This document focuses entirely on the Code tab, which runs Claude Code sessions.
The app maintains two parallel storage systems: a Chromium-backed browser storage (leveldb for Local Storage and IndexedDB), and a structured file hierarchy in %APPDATA%\Claude\. The browser storage caches server-fetched data. The file hierarchy stores the durable local state — session metadata, transcripts, worktrees, extensions.
All app data lives under %APPDATA%\Claude\ on Windows:
%APPDATA%\Claude\
Local Storage/leveldb/ ← Chromium Local Storage (browser-side, leveldb)
IndexedDB/ ← Chromium IndexedDB (browser-side, leveldb)
Session Storage/ ← Chromium Session Storage (per-tab, ephemeral)
Partitions/ ← Per-origin sandboxed storage for MCP iframes
claude-code-sessions/ ← Session metadata registry (one JSON per session)
<account-uuid>/
<workspace-uuid>/
local_<session-uuid>.json
local-agent-mode-sessions/ ← Session transcripts (JSONL conversation logs)
<account-uuid>/
<workspace-uuid>/
local_<session-uuid>/
audit.jsonl
.claude/projects/
-sessions-<human-name>/
<cli-session-uuid>.jsonl
subagents/
agent-<id>.jsonl
agent-acompact-<id>.jsonl
claude-code-vm/ ← VM/sandbox state for remote sessions
agents/ ← Installed agent extensions
Claude Extensions/ ← Installed connector extensions (MCP wrappers)
Claude Extensions Settings/ ← Per-extension config
git-worktrees.json ← Registry of active git worktrees (all accounts)
config.json ← UI preferences (theme, scale, locale, allowlists)
buddy-tokens.json ← Daily token usage counter
claude_desktop_config.json ← MCP server declarations
window-state.json ← Last window position/size
Local State ← Chromium DPAPI key store (for cookie encryption)
logs/ ← Diagnostic logs
Crashpad/ ← Crash reports
Example account UUIDs:
| UUID prefix | Account | Session type | Sessions | |
|---|---|---|---|---|
aabbccdd |
account-a | user@example.com | Local | 10 |
11223344 |
account-b | user@company.com | Local | 22 |
55667788 |
account-c | user@gmail.com | Remote (cloud VM) | 40 |
Account emails are stored in local-agent-mode-sessions/<account-uuid>/<workspace-uuid>/local_<session-uuid>.json → emailAddress field.
Account UUIDs are assigned by Anthropic's server. To identify them: read ~/.claude/.claude.json → oauthAccount.accountUuid for the currently logged-in CLI account.
Claude Code has two distinct session systems that share the brand name but are architecturally different.
| Property | Desktop Code-tab sessions | CLI terminal sessions |
|---|---|---|
| ID format | local_<uuid> |
<uuid> (no prefix) |
| Where shown | Desktop sidebar | claude --resume picker |
| Storage | %APPDATA%\Claude\claude-code-sessions\ + server cache |
~/.claude/projects/<encoded-cwd>/*.jsonl |
| Account-bound? | Metadata is account-scoped (stored in account subfolder) | No — pure local files |
| Server-dependent? | Sidebar list fetched from server; transcript stored locally | Fully local |
| Resume flag | No native CLI flag | claude --resume <uuid> |
Critical: these IDs don't overlap. A local_<uuid> session never appears in the CLI picker and vice versa.
claude-code-sessions/ — Session Metadata RegistryEach Desktop session has one JSON file at:
%APPDATA%\Claude\claude-code-sessions\<account-uuid>\<workspace-uuid>\local_<session-uuid>.json
{
"sessionId": "local_<session-uuid>",
"cliSessionId": "<cli-session-uuid>",
"cwd": "C:\\Projects\\my-project",
"originCwd": "C:\\Projects\\my-project",
"createdAt": 1777168830743,
"lastActivityAt": 1777171587818,
"model": "claude-opus-4-7[1m]",
"effort": "medium",
"isArchived": false,
"title": "dev",
"titleSource": "user",
"permissionMode": "acceptEdits",
"completedTurns": 5,
"enabledMcpTools": { "<tool-hash>": true, ... },
"remoteMcpServersConfig": [...],
"alwaysAllowedReasons": [...]
}
| Field | Purpose |
|---|---|
sessionId |
Desktop session ID — matches sidebar, worktree registry, leveldb references |
cliSessionId |
UUID pointing to the JSONL transcript in local-agent-mode-sessions/. This is a plain UUID (no local_ prefix) — same format as CLI session IDs |
cwd |
Working directory when session was created |
titleSource |
"user" (manually renamed) or "auto" (generated) |
permissionMode |
"default", "acceptEdits", "plan", "auto", "bypassPermissions" |
isArchived |
Set to true when session is archived (hidden from sidebar by default) |
completedTurns |
Number of user→assistant exchange pairs |
enabledMcpTools |
Map of <tool-hash>: true for each enabled MCP tool this session |
The workspace-uuid in the path is a Desktop-internal grouping UUID — not user-visible, not the session ID.
local-agent-mode-sessions/ — Session TranscriptsThe full conversation transcript for each Desktop session lives at:
%APPDATA%\Claude\local-agent-mode-sessions\
<account-uuid>\<workspace-uuid>\local_<session-uuid>\
audit.jsonl
.claude\projects\-sessions-<human-name>\
<cliSessionId>.jsonl ← main transcript
subagents\
agent-<id>.jsonl ← subagent transcripts
agent-acompact-<id>.jsonl ← compacted subagent transcripts
The <cliSessionId> in the filename matches cliSessionId in the metadata JSON. This is the bridge between the two storage locations.
The transcript JSONL contains multiple record types, identical in shape to CLI session JSONL files:
type="queue-operation" keys: type, operation, timestamp, sessionId, content
type="user" keys: parentUuid, isSidechain, userType, cwd, sessionId,
version, gitBranch, type, message, uuid, timestamp,
permissionMode
type="assistant" keys: parentUuid, isSidechain, userType, cwd, sessionId,
version, gitBranch, message, requestId, type, uuid,
timestamp
The user and assistant records are structurally identical to CLI JSONL records — the transcript format is shared.
audit.jsonlSeparate from the main transcript. Records operational events (session start, tool calls, errors) at a lower level. Not needed for conversation resume.
git-worktrees.json — Global Worktree RegistrySingle file at %APPDATA%\Claude\git-worktrees.json. Tracks all active worktrees across all sessions and accounts.
{
"worktrees": {
"<worktree-name>": {
"name": "<worktree-name>",
"path": "C:\\Projects\\my-project\\.claude\\worktrees\\<worktree-name>",
"leasedBy": "local_<session-uuid>",
"baseRepo": "C:\\Projects\\my-project",
"branch": "claude/<worktree-name>",
"sourceBranch": "main",
"createdAt": 1777163079076
}
}
}
leasedBy contains the Desktop local_<uuid> session ID that owns the worktree.
Location: %APPDATA%\Claude\Local Storage\leveldb\
Origin: https://claude.ai
Format: Chromium-encoded LevelDB. Requires ccl_chromium_reader to read (not plyvel — no Windows wheels).
Safety rule: Always copy the dir and delete the LOCK file before reading. Never open the live store.
https://claude.ai)| Key | Survives logout? | Shape | Purpose |
|---|---|---|---|
react-query-cache-ls |
No — cleared on logout | JSON, 10–20MB | Server query cache: conversation list, account info, model availability |
react-query-cache (IndexedDB mirror) |
No | Same content | Duplicate of above in IndexedDB keyval-store |
dframe-store |
Yes | {state: {pinnedOrder, sidebarWidth, lastKnownMode, groupByByMode}} |
Sidebar layout — pin order, width, grouping mode |
ccd-session-store |
Yes | {state: {selectedFolder}} |
Active project folder |
epitaxy.sidePaneStore.v1 |
Yes | {state: {tileLayoutBySession: {local_<uuid>: layout}}} |
Per-session pane layouts (keyed by session ID) |
epitaxy-unread-v1 |
Yes | {state: {unreadIds: []}} |
Unread session markers |
__qk_hint_account_uuid |
Yes (multi-account) | string | Last-active account UUID — multiple records may exist across accounts |
lastLoginMethod |
Yes | string | e.g. "google" |
default-model |
Yes | string | e.g. "claude-opus-4-7" |
branch-status-cache |
Yes | JSON | Cached git branch status per repo |
Append-only gotcha: LevelDB appends new writes; old values persist until compaction. Always iterate all records and keep the last value per key — never trust the first hit.
Mutation: Quit Claude Desktop → edit via Node level package or direct leveldb write → restart. Or use --remote-debugging-port=<n> for live DevTools access.
MCP tool widgets run in iframes scoped to per-session origins:
https://<hash>.claudemcpcontent.com/^0https://claude.ai
Each MCP iframe has its own isolated Local Storage (Chromium's ^0 storage partition suffix). State persists across sessions for that widget — e.g. Asana task drafts cache per-widget. These are stored in %APPDATA%\Claude\Partitions\.
Desktop sessions run in one of three environments (set at session creation):
| Environment | cwd pattern |
Transcript stored locally? |
|---|---|---|
| Local | C:\... / X:\... (Windows path) |
Yes — JSONL in local-agent-mode-sessions/<account>/<ws>/local_<id>/.claude/projects/ |
| Remote (Anthropic cloud VM) | /sessions/<name>/mnt |
No — server-side only |
| SSH | /<remote-path> |
No — server-side only |
Local sessions write a full JSONL transcript to disk as the session runs. These transcripts are account-agnostic bytes — any account can read and replay them.
Remote sessions (Anthropic-hosted, selected via the "Remote" environment option) run in an ephemeral cloud VM. The conversation transcript is stored server-side under the creating account. The local local-agent-mode-sessions directory for remote sessions contains only workspace environment files (.claude/.claude.json, debug logs) — no conversation JSONL.
This distinction determines what cross-account session transfer is possible:
Observed pattern:
C:\Projects\... (a Windows path)/sessions/<vm-name>/mnt| Storage | Account-scoped? | Survives logout? | Notes |
|---|---|---|---|
~/.claude/projects/*.jsonl |
No | Yes | CLI transcripts — pure local, zero account markers |
~/.claude/.claude.json |
Yes | Replaced on login | CLI auth state |
claude-code-sessions/<account-uuid>/ |
Yes (by path) | Yes | Metadata files remain on disk, accessible by path |
local-agent-mode-sessions/<account-uuid>/ |
Yes (by path) | Yes | Transcript JSONLs remain on disk |
react-query-cache-ls |
Yes | No — cleared | Server-fetched sidebar list |
dframe-store.pinnedOrder |
No | Yes | Pin order persists; references server IDs |
epitaxy.sidePaneStore.v1 |
No | Yes | Tile layouts persist; reference local session IDs |
git-worktrees.json |
No | Yes | Worktree registry persists |
Key insight: When you switch accounts, the Desktop sidebar repopulates from the new account's server-fetched react-query-cache-ls. Prior sessions are not gone — their metadata and transcripts remain on disk under the old account's UUID subdirectory.
CLI resume of a Desktop session (account-agnostic):
Any Desktop session can be resumed in the CLI terminal from any account, because the transcript JSONL is a local file:
claude-code-sessions/<account-uuid>/*/local_<session-uuid>.jsoncliSessionId from that JSONlocal-agent-mode-sessions/<account-uuid>/*/local_<session-uuid>/.claude/projects/-sessions-*/< cliSessionId>.jsonl~/.claude/projects/<encoded-cwd>/<cliSessionId>.jsonlclaude --resume <cliSessionId> — works regardless of logged-in accountDesktop-to-Desktop cross-account (VALIDATED — 2026-04-26):
Confirmed working: copying only the session metadata JSON from account A's claude-code-sessions/ dir into account B's claude-code-sessions/ dir is sufficient for the session to appear in account B's sidebar and accept new messages.
How Desktop resolves the transcript: Desktop reads cliSessionId from the metadata JSON, then loads the transcript from ~/.claude/projects/<encoded-cwd>/<cliSessionId>.jsonl — which is account-agnostic. It does NOT validate session ownership against the server on click. No server gating observed.
Minimal procedure (battle-tested):
# 1. Find sessions to transfer
A_SESSIONS="$APPDATA/Claude/claude-code-sessions/<A-uuid>/"
B_WORKSPACE="$APPDATA/Claude/claude-code-sessions/<B-uuid>/<any-existing-workspace-uuid>/"
# 2. For each session, verify the JSONL bridge exists
cliSessionId=$(jq -r '.cliSessionId' session.json)
ls ~/.claude/projects/<encoded-cwd>/$cliSessionId.jsonl # must exist
# 3. Copy just the metadata JSON
cp "$A_SESSIONS/<ws>/local_<uuid>.json" "$B_WORKSPACE/local_<uuid>.json"
# 4. Sessions appear in account B's sidebar immediately (no Desktop restart needed)
# Desktop scans the filesystem for sessions — does NOT rely on dframe-store.pinnedOrder
What is NOT needed:
local-agent-mode-sessions/ transcript directoriesdframe-store.pinnedOrder in leveldbSidebar refresh behaviour (important):
Ctrl+R — reloads the renderer from cached state only. Does NOT retrigger filesystem scan. Copied sessions will NOT appear.react-query-cache-ls server fetch AND full filesystem rescan. Copied sessions WILL appear. This is the required step.Sidebar discovery — how it actually works (validated via leveldb probe):
The sidebar is populated from two sources, unioned together:
react-query-cache-ls (20MB blob). Contains only sessions the server knows about for the current account.claude-code-sessions/<current-account-uuid>/ and loads all local_*.json files found. These appear in the sidebar regardless of whether the server knows about them.Transferred sessions (copied from another account's dir) appear via path #2 — the filesystem scan. Confirmed: 7 of 8 transferred sessions were ABSENT from react-query-cache-ls but appeared in the sidebar. The 8th appeared in the cache only as a previously-cached transcript (epitaxy.local.transcript.* key), not as a session list entry.
dframe-store.pinnedOrder only controls pinned-to-top ordering — all other sessions are shown below, sorted by lastActivityAt. Confirmed: pinnedOrder had 2 entries while 40+ sessions appeared in the sidebar.
Validated transfers (2026-04-26):
Bulk copy script:
EVO="$APPDATA/Claude/claude-code-sessions/<A-uuid>"
TARGET="$APPDATA/Claude/claude-code-sessions/<B-uuid>/<workspace-uuid>"
JSONL_DIR="$HOME/.claude/projects/<encoded-cwd>"
for ws in "$EVO"/*/; do
for f in "$ws"*.json; do
[ -f "$f" ] || continue
cwd=$(jq -r '.cwd // ""' "$f")
[ "$cwd" = "C:\\Projects\\TargetProject" ] || continue
cli=$(jq -r '.cliSessionId' "$f")
[ -f "$JSONL_DIR/$cli.jsonl" ] || { echo "SKIP (no JSONL): $f"; continue; }
cp "$f" "$TARGET/$(basename "$f")"
echo "OK: $(jq -r '.title' "$f") → $(basename "$f")"
done
done
claude --resume using the local_<uuid> ID — wrong format; CLI only accepts plain UUIDs/sessions/<vm-name>/mnt) — no local JSONL exists to bridgeimport json, pathlib, sys
base = pathlib.Path(r'C:/Users/<user>/AppData/Roaming/Claude/claude-code-sessions')
sessions = []
for account_dir in sorted(base.iterdir()):
if not account_dir.is_dir(): continue
for ws in account_dir.iterdir():
if not ws.is_dir(): continue
for f in ws.glob('local_*.json'):
try:
d = json.loads(f.read_text(encoding='utf-8'))
sessions.append({
'account': account_dir.name[:8],
'sessionId': d['sessionId'],
'cliSessionId': d.get('cliSessionId', ''),
'title': d.get('title', '?'),
'cwd': d.get('cwd', ''),
'isArchived': d.get('isArchived', False),
'lastActivity': d.get('lastActivityAt', 0),
'_path': str(f)
})
except: pass
sessions.sort(key=lambda x: x['lastActivity'], reverse=True)
for s in sessions:
print(f"[{s['account']}] {s['title']!r:<20} cli:{s['cliSessionId'][:8]} {s['cwd'][-40:]}")
def find_transcript(appdata, account_uuid, desktop_session_uuid, cli_session_id):
base = pathlib.Path(appdata) / 'Claude' / 'local-agent-mode-sessions'
pattern = f'**/local_{desktop_session_uuid}/**/{cli_session_id}.jsonl'
results = list((base / account_uuid).rglob(f'{cli_session_id}.jsonl'))
return results[0] if results else None
# Copy live store (LOCK copy will warn — fine)
cp -r "$APPDATA/Claude/Local Storage/leveldb" /tmp/ls-probe
rm -f /tmp/ls-probe/LOCK
# Install reader (GitHub only, not on PyPI)
uv pip install "git+https://github.com/cclgroupltd/ccl_chrome_indexeddb.git"
# Dump all keys
python skills/leveldb-ops/scripts/dump_localstorage.py /tmp/ls-probe --origin https://claude.ai
# Extract specific keys
python skills/leveldb-ops/scripts/extract_keys.py /tmp/ls-probe dframe-store ccd-session-store
Stored in ~/.claude/.claude.json → cachedGrowthBookFeatures. The app uses these to gate experimental features. Prefix is tengu_. Some notable ones observed:
| Flag | Likely purpose |
|---|---|
tengu_worktree_mode |
Git worktree session isolation |
tengu_ccr_bridge_multi_session |
Multi-session bridge mode |
tengu_session_memory |
Session memory features |
tengu_auto_mode_config |
Auto permission mode |
tengu_kairos_loop_dynamic |
Dynamic loop pacing |
tengu_sm_compact |
Smart compaction |
tengu_harbor |
Sidebar/session management features |
tengu_relay_chain_v1 |
Agent relay chaining |
tengu_mcp_elicitation |
MCP tool elicitation |
These are read-only observations — modifying them locally has unknown effects.
| File | Path | Contents |
|---|---|---|
claude-desktop-state.md |
skills/leveldb-ops/references/ |
Full Local Storage key map for https://claude.ai origin |
chromium-format.md |
skills/leveldb-ops/references/ |
LevelDB on-disk format, locking, append semantics |
dump_localstorage.py |
skills/leveldb-ops/scripts/ |
Full Local Storage dump |
extract_keys.py |
skills/leveldb-ops/scripts/ |
Targeted key extraction |
dump_indexeddb.py |
skills/leveldb-ops/scripts/ |
IndexedDB dump |