сессия · · 8 мин чтения

Два аккаунта Claude на одном Mac: 345 строк на bash

Хотел держать личный и рабочий Claude открытыми рядом, с разными иконками в Dock. Иконка слетала после обновлений, и в итоге вместо инструмента на 7 000 строк получился маленький скрипт ровно под мои задачи.

7 051 → 541 → 525 → 345 строк кода

Read in English

Я плачу за две подписки на Claude: личную и рабочую. Хотелось одновременно пользоваться обоими аккаунтами и в Claude Desktop, и в Claude Code, входить в каждый отдельно, а в Desktop ещё и различать их по иконкам, чтобы не набрать рабочий промпт в личном окне. Труднее всего оказалось сделать так, чтобы иконки оставались разными после обновления.

В итоге получился claude-profiles: один bash-скрипт на 345 строк, ставить дополнительно ничего не нужно. Большая часть кода нужна, чтобы учесть неожиданное для меня поведение macOS. А чтобы скрипт остался маленьким, пришлось отказаться от функций, которые мне не нужны.

С Claude Code всё просто

Claude Code умеет брать настройки, память и данные входа профиля из отдельной папки конфигурации:

CLAUDE_CONFIG_DIR=~/.claude-work claude

Для этой папки нужно отдельно войти в аккаунт. Данные аккаунта хранятся в поле oauthAccount файла .claude.json в той же папке — по ним легко проверить, какой аккаунт связан с профилем. Несколько месяцев я запускал эту команду через алиас в оболочке.

С Desktop сложнее

Claude Desktop написан на Electron, и через --user-data-dir ему можно дать отдельную папку данных. Проблема была в Dock. В Dock отображается иконка самого запущенного приложения, поэтому два экземпляра одного Claude.app выглядят одинаково. А я хотел различать их с первого взгляда.

Какое-то время я пользовался claude-multiprofile. Он делает APFS-клон Claude.app под каждый профиль, перекрашивает иконку клона и создаёт небольшое приложение-лаунчер для Dock. Клон создаётся практически мгновенно и поначалу почти не требует дополнительного места на диске. Всё прекрасно работало.

А потом однажды моя чёрная рабочая иконка снова оказалась оранжевой.

Иконка, которая всё время возвращалась

Иконка профиля хранилась в метаданных Finder: скрытый файл Icon\r внутри бандла плюс флаг. При обновлении Claude заменял бандл, и иконка профиля пропадала. Dock показывал исходную иконку запущенного приложения, и рабочий Claude снова выглядел ровно как личный.

Вернуть иконку руками из терминала получалось. Автоматически — нет. Я завёл launchd-агент, который возвращал иконку после каждого обновления, и он падал с ошибкой:

launchd-агент
touch: …/Claude work.app/.wtest: Operation not permitted

Выяснилось, что дело в App Management: эта защита macOS ограничивает изменения бандлов приложений. У терминала разрешение было, у фоновой задачи нет. Можно было выдать разрешение вспомогательному приложению. Но сначала я хотел понять, как это устроено, поэтому попросил Claude собрать крошечный апплет-пробник и проверить, какие копии он может менять.

Вот что я увидел:

  • Копию Claude.app, которую пробник сделал сам, он менял свободно.
  • Копию, сделанную claude-multiprofile, менять не мог.
  • Атрибуты com.apple.provenance у копий различались в зависимости от того, какой процесс их создал.

В этих тестах приложение могло менять бандлы, которые создало само. На этом наблюдении я и построил схему: копию Claude для профиля создаёт и меняет только лаунчер в Dock. По клику он проверяет, не обновился ли Claude, при необходимости пересобирает копию, возвращает иконку и открывает её. В моих тестах всё это обходилось без запроса разрешения App Management. Демона, который следит за обновлениями, нет. Починка происходит, когда я открываю профиль.

Доверять ли инструменту, который уже стоит?

Прежде чем что-то переписывать, я попросил Claude проверить установленную у меня версию claude-multiprofile, 0.1.31. Claude сообщил, что сетевого кода вне команды upgrade нет, скриптов установки нет, к Keychain инструмент не обращается, а установленный код совпадает с опубликованным пакетом. Инструмент мне нравился.

Но версия инструмента на Node, которой я пользовался, содержала 7 051 строку кода. Поддерживал инструмент один мейнтейнер, а устанавливался он через npm. Чтобы разобраться во всём коде, который я запускаю, пришлось бы вычитывать изменения при каждом обновлении. К тому же инструмент умел намного больше, чем мне нужно.

Я попросил переписать его, и требований было немного:

  • Код легко читается.
  • Мой сценарий с двумя аккаунтами работает лучше, чем в оригинале.
  • Любое число профилей, без дополнительной логики для третьего и следующих.
  • bash и osascript, никакого рантайма, который надо ставить.
  • Репозиторий публичный, чтобы читать код могли и другие.

Профиль — это просто имя

Для N профилей отдельная логика не нужна, если все пути выводятся из имени профиля. Ни реестра, ни отдельного файла с настройками профиля. Есть папка профиля, значит есть профиль. Для work:

~/Applications/Claude work.app                          лаунчер для Dock
~/Library/Application Support/claude-profiles/work/     копия Claude, её иконка и лог
~/Library/Application Support/Claude-work               Desktop: данные входа, чаты
~/.claude-work                                          Claude Code: данные входа, настройки, память
~/.local/bin/claude-work                                команда для терминала

Это соответствие задаёт одна функция, load_profile. Остальной скрипт пользуется переменными, которые она выставляет.

Чему меня научила macOS

Каждый из этих нюансов обернулся хотя бы одним неудачным запуском.

  • Самообновление может открыть не тот аккаунт. Когда копия обновлялась сама, апдейтер перезапускал её без --user-data-dir, и она поднималась уже с моим основным аккаунтом. Claude читает политику «блокировать автообновления» из папки рядом со своей папкой данных (Claude-work-3p/configLibrary/). Скрипт пишет эту политику, только если папки Claude-work-3p ещё нет: в ней могут лежать настройки сторонних провайдеров пользователя.
  • Переключение на приложение может его перезапустить. Если в момент клика по лаунчеру приложение как раз закрывается, open -a может запустить его заново без флагов профиля. Поэтому лаунчер всегда передаёт полный набор параметров --env и --args, даже когда собирается просто вывести уже открытое окно на передний план.
  • Ссылка для входа может попасть не в то приложение. После входа через браузер возврат идёт по ссылке claude://. У копии тот же идентификатор бандла, поэтому macOS может отдать ссылку основному Claude. Обходной путь подсказывает документация самого claude-multiprofile: выбрать «Continue with email» и ввести код руками или закрыть основной Claude на время входа.
  • При запуске из Dock приложение не наследует PATH из оболочки. Лаунчер вызывает скрипт по полному пути, а скрипт сам выставляет себе PATH.
  • У апплетов, которые я собирал через osacompile, не было идентификатора бандла. PlistBuddy Set падал, потому что такого ключа не существовало, нужен был Add. При включённом set -e эта ошибка останавливала весь скрипт.
  • Диагностическое сообщение PlistBuddy попадало в stdout. Чтение версии ещё не созданной копии возвращало File Doesn't Exist, Will Create. Скрипт принимал это сообщение за версию и так и не собирал копию.
  • Bash игнорирует set -e внутри функции, вызванной как f || handle_error. Функция пересборки копии вызывается именно так, поэтому каждый шаг в ней обязан сам проверять свой результат.
  • Файловая система на моём Mac не различает регистр. Прибираясь за старым инструментом, Claude отправил в Корзину Claude WORK.app. Путь совпадал с новеньким Claude work.app, так что новый лаунчер тоже уехал в Корзину, а за ним и закреплённая иконка в Dock. Я спросил, не старый ли демон иконок это сделал. Claude ответил: «Нет, это я. Демон тут ни при чём». Одна команда add work — и лаунчер вернулся.

От чего я отказался

Claude, а потом и ревьюеры на GPT предлагали функции, которые я решил не оставлять.

  • Проверка аккаунта в начале каждой сессии Claude Code. Её предложил друг, и я понимал, чем она хороша, но отдельная проверка на старте сессии мне была не нужна. Проверку я оставил в claude-work: она запускается один раз перед стартом Claude Code и молчит, когда всё в порядке.
  • Диалог «вы не в том аккаунте». Если закрепить в Dock иконку запущенного приложения вместо иконки лаунчера, копия стартует без своих флагов и открывает основной аккаунт. Claude сделал диалог, который должен был это ловить. Потом я нажал на закреплённую иконку, попал не в тот аккаунт и никакого предупреждения не увидел. Проверка жила в лаунчере, а лаунчер в этом случае вообще не запускался. Я спросил: «То есть по сути бесполезно?» Для этого сценария так и было. Я убрал диалог, а заодно и состояние WRONG в выводе статуса.
  • Предупреждение о закреплённой иконке приложения. Я закрепляю её сознательно и риск принимаю, так что предупреждение откатил.
  • Предупреждение про регистр в README, которое Claude добавил после того, как сам на этом споткнулся. Мой ответ: «Не, надо проще».
  • Поддержка fish, автоматический выбор профиля по папке и ещё несколько идей для более специфичных сценариев.

Как я сокращал код с помощью трёх моделей

Код писал Claude (Opus). Для ревью я сознательно брал модель другого семейства, GPT через Codex, в надежде, что она заметит ошибки, которые автор пропустил. В одном из раундов сокращения кода Fable, ещё одна модель Claude, тоже подготовила свой, более короткий вариант для сравнения.

Первое ревью проводила gpt-6.1-sol с настройкой reasoning effort на максимуме — и нашла реальные баги. Худший сидел в функции пересборки: она могла удалить старую копию после неудачного копирования, потому что в этом контексте set -e игнорировался. Ещё ревью нашло изменение файла настроек оболочки, которое могло обрезать .zshrc, если пропадёт маркер, и проверку аккаунта, которая могла прочитать email из настроек MCP-сервера вместо данных входа. Все эти баги по моей просьбе исправили.

Дальше ревью были про размер. Каждый раунд я спрашивал: «Что можно выкинуть, ничего не потеряв?» Принимал те сокращения, после которых сохранялось нужное мне поведение.

ЭтапСтрок
claude-multiprofile7 051 (Node)
Первая версия541
Убран диалог «не тот аккаунт»525
Исправлены баги из первого ревью530
Комментарии заменены именами (63 строки комментариев → 19)495
Одна команда check, вывод функций для оболочки, перестановка кода в файле397
Одна функция для иконок, хелперы покороче327
Команды как симлинки в ~/.local/bin345

Несколько изменений оказались особенно удачными:

  • Комментарии стали именами. Я позаимствовал правило из другой кодовой базы: комментарий — повод поискать имя получше. # set_icon <image> <app> превратился в local image=$1 app=$2. Оставшиеся комментарии объясняют причины решений в коде, в том числе тех, что связаны с поведением macOS из списка выше.
  • list и check слились в одну команду. Она выводит профили и связанные с ними аккаунты, а затем — найденные проблемы и способы их исправить. Когда ничего не сломано, отдельное «всё хорошо» не нужно.
  • Скрипт перестал править .zshrc. Первая версия вписывала туда помеченный блок. Следующая печатала функции для eval. Теперь add кладёт симлинки в ~/.local/bin, ту самую папку, которую использует нативный установщик Claude Code. Профиль скрипт определяет по имени, под которым его вызвали. Если эта папка уже есть в PATH, команда работает и в открытых терминалах, без изменения файлов настройки оболочки и без запуска чего-либо при её старте.

Не каждое сокращение я принял. Один ревьюер предложил убрать цветные иконки (−37 строк) и оставить один вариант настроек (−18). Для себя я бы пережил и то, и другое. Но профиль без своей иконки выглядит в Dock ровно как основной Claude. А это та самая проблема, которую я хотел решить, так что цвета остались.

Как попробовать

Нужны macOS, Claude Desktop в /Applications и Claude Code.

git clone https://github.com/ishaba/claude-profiles ~/.local/share/claude-profiles
~/.local/share/claude-profiles/claude-profiles add work

Перетащите ~/Applications/Claude work.app в Dock, откройте и войдите через «Continue with email». В терминале запустите claude-work и войдите в тот же аккаунт. claude-profiles check сообщит о проблемах, если найдёт их.

Инструмент неофициальный: он опирается на флаг Electron и на то, как Claude Desktop читает свои данные и настройки обновлений, так что очередной релиз Claude может его сломать. Если так случится, разбираться и чинить придётся в одном файле.

Чьи это идеи

Основные идеи взяты из claude-multiprofile (MIT): копия приложения ради отдельной иконки, блокировка самообновлений через папку -3p, перенос MCP-серверов и вход по коду из письма. Мой вклад скромнее. Я разобрался, почему фоновая починка иконки не работала, и на основе увиденного перенёс её в лаунчер, без постоянного фонового процесса.


Если claude-profiles сэкономил вам полдня, поставьте ему звёздочку на GitHub: это может помочь другим пользователям с двумя аккаунтами Claude найти инструмент. А если наткнётесь на причуду macOS, которую я упустил, заведите issue — хотелось бы добавить её в список.