---
name: claude-code-desktop-sessions-storage
description: Где Claude Code desktop хранит список чатов и как «синхронизировать»/консолидировать чаты между командами на одном Mac
metadata: 
  node_type: memory
  type: reference
  originSessionId: 5c2bd44d-ef6f-4f26-8f09-de2abd31717c
  modified: 2026-08-13T16:58:45.343Z
---

Claude Code **desktop** хранит каждый чат отдельным JSON-файлом локально:
`~/Library/Application Support/Claude/claude-code-sessions/<accountUuid>/<organizationUuid>/local_<sessionId>.json`.

Сайдбар фильтруется по **organizationUuid** (средняя папка) — поэтому при переключении команды видны разные чаты. org-id НЕ зашит внутрь файлов, только в путь папки → чаты можно «перенести» в другую команду, скопировав файлы в её org-папку.

Реальные транскрипты лежат отдельно и **org-независимо**: `~/.claude/projects/<encoded-cwd>/<cliSessionId>.jsonl` (поле `cliSessionId` в метафайле линкует). Папка `~/.claude/projects/` — это CLI-история, НЕ источник сайдбара desktop.

Аккаунт cebers227@gmail.com: accountUuid `6050c8e8-cc0f-49a9-83fa-9ffcbcf10190`. Команды (org):
- `1447fa9f-978d-4b64-852b-c12da7699be0` — старая команда, ~49 чатов (вся история: Недвижка/ВинКод/Парсер AI…)
- `559c8266-7d53-465f-8ab5-4d6a7d793e79` — «Eventmyteam» (team_tier_1, max_5x)
- `a16cd684-2e20-415c-a16f-92ff88b3badf` — команда, использовавшаяся до 13.08.2026
- `c26a05b8-3aeb-4570-a67c-9e0262030cf9` — команда с 13.08.2026, текущая

**Первый вход в новую команду ВСЕГДА выглядит как «сессии пропали».** Приложение читает список чатов org один раз — когда впервые в неё заходит; папки новой org тогда ещё нет, в логе `Loaded 0 persisted sessions`. Синхронизатор создаст её и разнесёт пул в течение 60 с, но приложение туда больше не смотрит → нужен **один перезапуск Claude**, и всё на месте. Настраивать ничего не надо, новую org скрипт подхватывает сам (в логе растёт «каталогов: N»). Предсоздать папку заранее нельзя — UUID org неизвестен до переключения.

**2026-08-08: симлинк вместо каталога аккаунта БОЛЬШЕ НЕ РАБОТАЕТ.** С билда от 05.08 приложение создаёт каталог хранилища через `open(O_RDONLY|O_DIRECTORY|O_NOFOLLOW)` — на симлинке macOS отдаёт **ENOTDIR**, и запись реестра отваливается целиком (чтение при этом работает, поэтому поломка незаметна: список чатов просто замирает, а всё новое живёт в оперативке до перезагрузки). Диагностика: `grep ENOTDIR ~/Library/Logs/Claude/main.log`.

Поэтому объединение чатов между аккаунтами теперь = настоящие каталоги + синхронизация:
`~/.claude/tools/ccd-sessions-sync.py` под LaunchAgent `dev.cebers.ccd-sessions-sync` (раз в 60 с, лог `~/Library/Logs/ccd-sessions-sync.log`). Сводит `_shared_pool` и все `<accountUuid>/<orgUuid>/`: побеждает карточка с большим `lastActivityAt`, `deleted_<id>` (внутри timestamp мс) убивает чат, если не старше карточки. Старые симлинки лежат рядом как `*.symlink-bak`. Новые карточки приложение подхватывает **только при старте** (в логе `Loaded N persisted sessions`), на лету — нет.

**Группы чатов (папки в сайдбаре) синхронизатором сессий НЕ переносятся** — они не в карточках. Источник правды один: **zustand-store сайдбара в localStorage рендерера** — `Local Storage/leveldb`, ключ `_https://claude.ai\0\1dframe-store`, значение = байт кодировки (`0`=UTF-16LE, `1`=UTF-8) + JSON `{state:{…},version}`, поле `state.customGroupsByScope["<accountUuid>/<orgUuid>"] = {groups,assignments,order}`. Текущая команда — в `state.lastSidebarScopeKey`; `assignments` ссылаются на имена карточек (`code:local_<id>`). Новая команда = нет своего scope = групп нет.

**Два зеркала, на которые я потратил пять перезапусков — писать в них бесполезно**, приложение перезаписывает их из `dframe-store`:
* `claude_desktop_config.json` → `preferences.epitaxyPrefs["dframe-group-scopes"]`;
* тот же localStorage, но ключ `LSS-persisted.dframe-group-scopes` (слой синхронизации между окнами, с полями `tabId`/`timestamp`).

Признак, что бьёшь в зеркало: патч физически в файле, приложение стартовало ПОСЛЕ него (проверить по `ps -eo lstart`) — и всё равно через пару секунд перезаписало значение своим. Значит источник другой. Искать по грепу имени группы среди ВСЕХ ключей стора, а не только в том, что нашёлся первым.

Лечит `~/.claude/tools/ccd-groups-sync.py` (караульный) + `~/.claude/tools/ccd-groups/patch-store.js` (запись в LevelDB через npm `classic-level`, установлен в `~/.claude/tools/ccd-groups/`), под LaunchAgent `dev.cebers.ccd-groups-sync`; лог `~/Library/Logs/ccd-groups-sync.log`, лок `~/Library/Logs/ccd-groups-sync.lock`, бэкапы каталога стора рядом как `leveldb.bak-*` (хранится 3). Заполняет только ПУСТЫЕ scope копией самого полного scope того же аккаунта — существующие не трогает, иначе удалённая в одной команде группа воскресала бы из соседней.

**Почему караулит, а не тикает** (каждая ловушка стоила перезапуска):
1. при живом Claude LevelDB держит LOCK — записать нельзя;
2. стор читается при старте рендерера, а **через ~4 с после старта приложение пишет ключ обратно из своей памяти** — то есть опоздавший патч не просто не виден, он затирается (видно по полю `timestamp` в значении: 19:32:57 против нашего 19:32:53);
3. окно между выходом и стартом — доли секунды, если приложение перезапускают сразу. Поэтому горячий путь должен быть коротким: бэкап делается APFS-клоном (`cp -Rc`, миллисекунды) — обычный `copytree` 17 МБ съедал секунду и проигрывал гонку.

Инстанс висит под локом, пока приложение живо (`pgrep` раз в 0.5 с), и патчит через 0.06 с после выхода, затем шлёт уведомление «можно открывать». После старта приложения ждёт 10 с и перепроверяет по клону стора: если патч затёрт — уведомляет «не успел, перезапустите». Тик LaunchAgent (10 с) нужен только чтобы поднять караульного заново. `node` вызывается абсолютным путём — launchd не наследует homebrew-PATH.

Отладка — на КОПИИ каталога `leveldb` (скопировать, удалить `LOCK`, открыть classic-level): копию можно читать и писать при живом приложении. Осторожно с `process.argv` в одноразовых node-скриптах: путь к БД это `argv[2]`, лишний аргумент впереди — и classic-level молча создаёт пустую базу, из-за чего живой стор выглядит как «0 ключей». Звёзды/пины (`starred-*`, `dframe-local-slice`) — глобальные, их это не касается.

2026-06-26: скопировал 49 чатов из 1447fa9f → 559c8266 (бэкап `~/.claude-team-sync-backup-20260626-145721`), чтобы консолидировать в Eventmyteam. **НЕ подтверждено**, что desktop покажет локальные копии после перезапуска — если приложение сверяет список с сервером по org, оно может их спрятать; тогда вариант = просто переключаться на старую команду через нижний свитчер. Нет официальной cross-org синхронизации/импорта чатов (Codex тоже просто читает локальные файлы, не синкает).
