# Claude Desktop Internals — File System, Storage, and Session Architecture > 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. --- ## Overview 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. --- ## 1. File System Layout 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) / / local_.json local-agent-mode-sessions/ ← Session transcripts (JSONL conversation logs) / / local_/ audit.jsonl .claude/projects/ -sessions-/ .jsonl subagents/ agent-.jsonl agent-acompact-.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 | Email | 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///local_.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. --- ## 2. Session ID Schemes — Two Separate Namespaces 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_` | `` (no prefix) | | Where shown | Desktop sidebar | `claude --resume` picker | | Storage | `%APPDATA%\Claude\claude-code-sessions\` + server cache | `~/.claude/projects//*.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 ` | **Critical:** these IDs don't overlap. A `local_` session never appears in the CLI picker and vice versa. --- ## 3. `claude-code-sessions/` — Session Metadata Registry Each Desktop session has one JSON file at: ``` %APPDATA%\Claude\claude-code-sessions\\\local_.json ``` ### Full Schema ```json { "sessionId": "local_", "cliSessionId": "", "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": { "": true, ... }, "remoteMcpServersConfig": [...], "alwaysAllowedReasons": [...] } ``` ### Key Fields | 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 `: 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. --- ## 4. `local-agent-mode-sessions/` — Session Transcripts The full conversation transcript for each Desktop session lives at: ``` %APPDATA%\Claude\local-agent-mode-sessions\ \\local_\ audit.jsonl .claude\projects\-sessions-\ .jsonl ← main transcript subagents\ agent-.jsonl ← subagent transcripts agent-acompact-.jsonl ← compacted subagent transcripts ``` **The `` in the filename matches `cliSessionId` in the metadata JSON.** This is the bridge between the two storage locations. ### JSONL Record Types 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.jsonl` Separate from the main transcript. Records operational events (session start, tool calls, errors) at a lower level. Not needed for conversation resume. --- ## 5. `git-worktrees.json` — Global Worktree Registry Single file at `%APPDATA%\Claude\git-worktrees.json`. Tracks all active worktrees across all sessions and accounts. ```json { "worktrees": { "": { "name": "", "path": "C:\\Projects\\my-project\\.claude\\worktrees\\", "leasedBy": "local_", "baseRepo": "C:\\Projects\\my-project", "branch": "claude/", "sourceBranch": "main", "createdAt": 1777163079076 } } } ``` `leasedBy` contains the Desktop `local_` session ID that owns the worktree. --- ## 6. Chromium Local Storage — LevelDB Schema 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. ### Key Entries (origin: `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_: 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=` for live DevTools access. --- ## 7. MCP Iframe Partitioning MCP tool widgets run in iframes scoped to per-session origins: ``` https://.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\`. --- ## 8. Local vs Remote Session Execution 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///local_/.claude/projects/` | | **Remote** (Anthropic cloud VM) | `/sessions//mnt` | No — server-side only | | **SSH** | `/` | 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: - Local → any account: transcript file copy + leveldb pin is feasible - Remote → different account: server-gated, no local transcript to copy **Observed pattern:** - Accounts running **local** sessions have cwd like `C:\Projects\...` (a Windows path) - Accounts running **remote** sessions have cwd like `/sessions//mnt` ## 8b. Account Binding — What Survives Account Switching | 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//` | Yes (by path) | Yes | Metadata files remain on disk, accessible by path | | `local-agent-mode-sessions//` | 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. --- ## 9. Cross-Account Session Continuity (Local Sessions Only) ### What's possible **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: 1. Find the session in `claude-code-sessions//*/local_.json` 2. Extract `cliSessionId` from that JSON 3. Find the JSONL at `local-agent-mode-sessions//*/local_/.claude/projects/-sessions-*/< cliSessionId>.jsonl` 4. Copy the JSONL to `~/.claude/projects//.jsonl` 5. Run `claude --resume ` — works regardless of logged-in account **Desktop-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//.jsonl` — which is account-agnostic. It does NOT validate session ownership against the server on click. No server gating observed. **Minimal procedure (battle-tested):** ```bash # 1. Find sessions to transfer A_SESSIONS="$APPDATA/Claude/claude-code-sessions//" B_WORKSPACE="$APPDATA/Claude/claude-code-sessions///" # 2. For each session, verify the JSONL bridge exists cliSessionId=$(jq -r '.cliSessionId' session.json) ls ~/.claude/projects//$cliSessionId.jsonl # must exist # 3. Copy just the metadata JSON cp "$A_SESSIONS//local_.json" "$B_WORKSPACE/local_.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:** - Copying `local-agent-mode-sessions/` transcript directories - Modifying `dframe-store.pinnedOrder` in leveldb **Sidebar refresh behaviour (important):** - `Ctrl+R` — reloads the renderer from cached state only. Does NOT retrigger filesystem scan. Copied sessions will NOT appear. - **Logout → Login** — triggers fresh `react-query-cache-ls` server fetch AND full filesystem rescan. Copied sessions WILL appear. This is the required step. - Live file creation while Desktop is open — fs.watch fires immediately, sessions appear without restart. **Sidebar discovery — how it actually works (validated via leveldb probe):** The sidebar is populated from two sources, unioned together: 1. **Server session list** — fetched from Anthropic's server on login, cached in `react-query-cache-ls` (20MB blob). Contains only sessions the server knows about for the current account. 2. **Filesystem scan** — on login, Desktop scans `claude-code-sessions//` 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):** - account-a → account-b: single session (29 turns, 1.8MB JSONL) — appeared, opened, accepted new messages ✓ - account-a → account-b: 8× sessions from a single repo (0–132 turns) — bulk copied ✓ **Bulk copy script:** ```bash EVO="$APPDATA/Claude/claude-code-sessions/" TARGET="$APPDATA/Claude/claude-code-sessions//" JSONL_DIR="$HOME/.claude/projects/" 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 ``` ### What's not possible - Accessing account A session **content** through the server while logged into account B — server-gated - Re-registering Desktop sidebar entries server-side from a different account — server-gated - Resuming a Desktop session via `claude --resume` using the `local_` ID — wrong format; CLI only accepts plain UUIDs - Transferring **remote** sessions (cwd: `/sessions//mnt`) — no local JSONL exists to bridge --- ## 10. Probe Recipes ### List all Desktop sessions across all accounts ```python import json, pathlib, sys base = pathlib.Path(r'C:/Users//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:]}") ``` ### Find JSONL for a Desktop session ```python 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 ``` ### Read leveldb Local Storage ```bash # 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 ``` --- ## 11. Feature Flags (GrowthBook / Tengu Flags) 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. --- ## 12. Related Files | 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 |