Два аккаунта Claude на одном Mac: 345 строк на bash
Хотел держать личный и рабочий Claude открытыми рядом, с разными иконками в Dock. Иконка слетала после обновлений, и в итоге вместо инструмента на 7 000 строк получился маленький скрипт ровно под мои задачи.
7 051 → 541 → 525 → 345 строк кода
Я плачу за две подписки на 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-агент, который возвращал иконку после каждого обновления, и он падал с ошибкой:
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-multiprofile | 7 051 (Node) |
| Первая версия | 541 |
| Убран диалог «не тот аккаунт» | 525 |
| Исправлены баги из первого ревью | 530 |
| Комментарии заменены именами (63 строки комментариев → 19) | 495 |
| Одна команда check, вывод функций для оболочки, перестановка кода в файле | 397 |
| Одна функция для иконок, хелперы покороче | 327 |
| Команды как симлинки в ~/.local/bin | 345 |
Несколько изменений оказались особенно удачными:
- Комментарии стали именами. Я позаимствовал правило из другой кодовой базы: комментарий — повод поискать имя получше.
# 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 — хотелось бы добавить её в список.