Как пульт переключает аккаунты Claude Code
На одной машине работают несколько подписок Claude Code. Пульт сам решает, на какой из них запускать каждую новую сессию агента, и переходит на следующую, когда текущая почти исчерпана. Ниже описано, как это устроено и почему именно так.
Коротко
Работа тратит один аккаунт, пока он не израсходован на 98 % по недельному лимиту или по пятичасовому окну. Потом новая работа уходит на следующий аккаунт по списку. Сессии на двух аккаунтах одновременно не работают никогда: пока на аккаунте идёт хоть одна живая сессия, новая работа встаёт туда же или ждёт. Начатая сессия продолжается на том аккаунте, где началась. Владелец может вручную закрепить новую работу за конкретным аккаунтом.
Словарь
- Пульт
- Локальный сервер владельца (кокпит) с веб-панелью. Видит все проекты на машине, запускает и останавливает сессии агентов, считает лимиты.
- Сессия
- Один запущенный Claude Code: процесс CLI в окне tmux, работающий в папке проекта.
- Наряд
- Заявка пульту «подними сессию в таком-то проекте с такой-то задачей» или «продолжи эту сессию». Наряды ставят владелец из панели и штаб.
- Штаб
- Агент-диспетчер, который ставит машинную работу без участия человека: просыпается по расписанию и раздаёт наряды.
- Вотчер
- Отдельный процесс рядом с пультом. Сервер пульта открыт в домашнюю сеть, поэтому сам процессы не запускает: он записывает намерение, а сессию поднимает вотчер.
- Аккаунт
- Одна подписка Claude (Pro или Max) со своими лимитами. В пульте у каждого аккаунта есть короткое имя; основной называется
main.
Зачем это нужно
У подписки Claude два ограничения: пятичасовое окно и недельный лимит. Пульт часто держит несколько агентов одновременно, и даже самый большой тариф выгорает раньше конца недели. Раньше в этот момент вся машинная работа останавливалась до сброса. С несколькими аккаунтами она переезжает на следующий и продолжается.
Что такое аккаунт технически
Claude Code хранит вход и настройки в каталоге конфигурации. По умолчанию это ~/.claude, но переменная окружения CLAUDE_CONFIG_DIR указывает CLI на другой каталог. На этом и построена схема:
- Основной аккаунт —
~/.claude, и сессия на нём запускается без переменной. Важно именно её отсутствие: даже если направить переменную на тот же каталог, CLI возьмёт другую запись в Связке ключей и другой.claude.json, то есть фактически другой вход. - Каждый следующий аккаунт — свой каталог
~/.claude-<имя>. Внутри полноценный вход через/login, как у обычного пользователя. - Общее у всех аккаунтов. Агенты, скиллы, глобальные правила и
settings.json(с хуками-заслонами) лежат в каталоге второго аккаунта символическими ссылками на основной. Поэтому агент на любом аккаунте ведёт себя одинаково. - Обязательные ссылки. Папки
projectsиsessionsтоже ведут в основной каталог: туда CLI пишет журналы сессий и реестр живых процессов, а пульт читает только~/.claude. Без этих ссылок пульт не увидел бы сессию второго аккаунта, поэтому такой аккаунт считается негодным.
Реестр аккаунтов — файл ~/.cockpit/claude-accounts.json, в нём имя, путь к каталогу и ожидаемая почта. Секретов там нет: токены хранит сам CLI в Связке ключей macOS, пульт знает только путь. Чтобы запустить сессию на втором аккаунте, вотчер поднимает tmux с -e CLAUDE_CONFIG_DIR=<каталог>. Если в окружении tmux или вотчера лежит ключ вроде ANTHROPIC_API_KEY, наряд проваливается с именем ключа: CLI молча предпочёл бы ключ входу каталога, и работа ушла бы на чужой счёт.
Владелец выбирал из двух способов: этот (отдельный каталог на аккаунт) и долгоживущий токен через CLAUDE_CODE_OAUTH_TOKEN. Выбран каталог. В этом варианте пульт не хранит учётных данных вовсе: в сессию передаётся только путь.
Откуда пульт знает остаток
Раз в 15 минут пульт собирает сводку лимитов по каждому аккаунту. Сначала он читает кэш, который CLI сам держит в .claude.json каталога. Если кэш устарел, пульт запускает claude -p /usage с окружением этого аккаунта и разбирает вывод. Для каждого аккаунта получается четыре числа: процент пятичасового окна, процент недели и время сброса каждого.
Сводка — снимок, поэтому в ней может стоять 100 % у окна, которое уже сбросилось. Правило ротации это учитывает: окно, у которого прошло время сброса, аккаунт не блокирует.
Пример, числа условные. Вертикальная риска на каждой шкале — порог 98 %. Хватает одного окна за риской, чтобы аккаунт перестал брать новую работу. Если на main ещё идут живые сессии, новая работа не уйдёт на second, а будет ждать (правило 2 ниже).
Как выбирается аккаунт для новой работы
Решение принимает одна функция сервера, newWorkPick. Её вызывают запуск наряда, будильник штаба и панель, которая показывает, куда пойдёт следующая сессия. Функция одна, чтобы панель не обещала один аккаунт, пока работа садится на другой. Проверки идут по порядку, первая сработавшая решает:
Ручной выбор владельца
Если в панели закреплён конкретный аккаунт, новая работа идёт туда. Если этот аккаунт ждёт входа или на другом аккаунте идут живые сессии, будет отказ с причиной, а тихого перехода на другой аккаунт не будет. Свою работу владелец может запустить на выбранном вручную аккаунте даже за порогом: это его решение. Машинной работе штаба исчерпанный аккаунт не достаётся никогда.
Где сейчас идёт работа
Если на каком-то аккаунте живут сессии, новая работа идёт только туда. Если этот аккаунт упёрся, ответ — отказ
409с текстом «на “main” живут 3 сессии, а “main”: пятичасовое окно 100 % (сброс 18:30)». Запуска на соседнем аккаунте при этом нет.Текущий аккаунт
Если живой работы нет, берётся «текущий» — тот, где запускался последний наряд Claude Code. Проваленные наряды не считаются. Пока текущий ниже 98 % по обоим окнам, работа остаётся на нём.
Следующий по кругу
Текущий упёрся — берётся следующий годный аккаунт в порядке реестра, по кругу. Аккаунты с известными обоими окнами идут раньше тех, чей остаток пульт пока не знает.
Отказ всем
Если упёрлись все, пульт отказывает с причиной по каждому аккаунту. Запуск на исчерпанном аккаунте бессмыслен: сессия умирает на первом же запросе к модели.
При одном аккаунте вся логика молчит, и поведение такое же, как до мульти-аккаунта: машинную работу держит порог проекта (по умолчанию 95 % недели), а работа владельца идёт без ограничений.
Почему один аккаунт за раз
Это прямое требование владельца: одновременно использовать два аккаунта небезопасно, и параллельные сессии на разных аккаунтах допускать нельзя. Второй аккаунт служит переливом, когда первый кончился, и не используется для удвоения параллельной работы.
Чтобы правило работало, пульт должен знать, где живёт работа. При каждом решении модуль account-live.ts смотрит процессы Claude Code через ps eww (результат кэшируется на несколько секунд): в окружении процесса видно, есть ли CLAUDE_CONFIG_DIR и какой каталог в нём указан. Поэтому учитываются и сессии, которые владелец запустил руками в терминале. К ним добавляются наряды, которые аккаунт уже заняли, но процесс ещё не поднялся.
- Сессия, которая простаивает дольше 20 минут, аккаунт не держит. Иначе забытая открытая вкладка навсегда привязала бы всю работу к одному аккаунту.
- Сессия, ждущая разрешения от человека, аккаунт держит, пока её не закроют.
- Служебные запуски самого пульта — замер лимитов и судья авто-наряда — работой не считаются, хотя CLI записывает их рядом с сессиями.
- Сессии на других платформах (Pi, Codex) не считаются: лимиты Claude они не тратят.
Единственное исключение
Если владелец пишет реплику агенту в чате панели, а аккаунт с живой работой упёрся, реплика уходит на аккаунт, где есть запас: человек ждёт ответа. Если запаса нет нигде, реплика получает отказ с причиной, и эту причину видно в чате.
Продолжение и перенос сессии
Сессия, которую продолжают, едет на тот аккаунт, где она реально шла последний раз. Пульт берёт его из своих нарядов. Если этот аккаунт упёрся, продолжение получает отказ «ждёт сброса»: на другой аккаунт сессия сама не переезжает.
Перенести сессию можно только явно: нарядом «продолжить» с остановкой текущего процесса и указанием нового аккаунта (kind: "resume", stopFirst: true, account). У переноса есть цена: на новом аккаунте кэш промптов холодный, и первый ход после переезда стоит дороже. Кнопки «перевести все живые сессии на другой аккаунт» пока нет, она заведена задачей.
Пример дня
main. Штаб ставит три наряда, все уходят на main.main пятичасовое окно 98 %. Три сессии ещё работают. Четвёртый наряд штаба получает отказ 409: на main живая работа, а сам он упёрся.main упёрся, поэтому следующий наряд уходит на second. Теперь текущий — second.main сбросилось. Работа при этом остаётся на second: ротация не возвращается назад, пока second сам не дойдёт до 98 %.Как добавить аккаунт
- Завести запись:
npm run account:add -- <имя>в папке пульта или кнопкой в блоке «Лимиты и расходы» панели. Пульт создаёт каталог~/.claude-<имя>и раскладывает в нём ссылки на общие папки. - Войти. Кнопка «Войти» в панели просит вотчер запустить
claude auth login --claudeaiдля этого каталога. Открывается окно Chrome с отдельным профилем под этот аккаунт, владелец входит своей почтой. Того же можно добиться командой, которую панель показывает для копирования в терминал. - Пульт проверяет вход по файлу каталога и не верит вотчеру на слово. Аккаунт негоден, если в каталог вошёл тот же пользователь, что и в основной (браузер подставил прежний вход, второй ёмкости это не даёт), если почта не совпала с ожидаемой или если не хватает обязательных ссылок. Причина показывается в панели.
Управление и диагностика
В панели, в блоке «Лимиты и расходы», у каждого аккаунта своя строка с обоими окнами. Переключатель «Новая работа: авто / <аккаунт>» задаёт ручной выбор, и отметка показывает, куда уйдёт следующая сессия. Переключатель действует только на сессии, которые запускает пульт. Сессия, запущенная вне пульта, в терминале или десктоп-приложении, идёт на тот аккаунт, который задан у неё в окружении: без CLAUDE_CONFIG_DIR это основной. Правило «один аккаунт за раз» её при этом учитывает.
Агентам то же доступно через API пульта:
| Запрос | Что делает |
|---|---|
GET /api/accounts/choice | Ручной выбор (или «авто»), куда прямо сейчас уйдёт новая работа владельца и штаба (или почему никуда) и на каких аккаунтах живут сессии. |
PUT /api/accounts/choice | Поставить аккаунт или null («авто»). Негодный аккаунт — 400, настройка не меняется. |
GET /api/summary → accounts | Лимиты по каждому аккаунту: проценты окон, время сброса, тариф, готовность. |
Когда работа стоит, хотя запас есть
- Живые сессии держат исчерпанный аккаунт. Если сброс близко, проще подождать. Если далеко, сессии пульта переносят по одной нарядом «продолжить» на аккаунт с запасом. Терминальные сессии владельца закрывает он сам.
- Вручную выбран исчерпанный аккаунт. Переключить на «авто» или на аккаунт с запасом.
- Наряд упал на лимите, хотя сводка показывала запас. Это дефект пульта, о нём заводят задачу на доску с выводом обеих ручек.
Ловушка ручного выбора
Если на аккаунте A идут живые сессии и в этот момент вручную выбрать аккаунт B, работа на A никуда не переедет, а вся новая работа, и штаба, и владельца, начнёт получать отказ, пока на A не станет пусто. Переключать стоит, когда на текущем аккаунте нет живой работы, и после переключения проверять GET /api/accounts/choice.
Проверка 1 октября: периметр и живой отказ
Правило «один аккаунт за раз» прошло ревью безопасности и проверку на живом пульте. Нашлись два дефекта с одним исходом: второй аккаунт мог подняться рядом с живой работой. Оба исправлены.
- Кривой pid ослеплял учёт. Запись реестра CLI с pid 0 или отрицательным проходила проверку «процесс жив», а
psна таком pid отказывает целиком. Из учёта разом пропадали все живые сессии. Подложить такую запись можно только с самой машины. Теперь учитывается только целый pid больше нуля. - Замер лимитов выглядел как работа. Пульт сам запускает
claude -p— замер/usageи судью авто-наряда, — и CLI пишет эти процессы в тот же реестр, что и сессии. Пока шёл замер, пульт видел работу наmainрядом с работой на втором аккаунте. При работе на обоих он сходится кmain, так что наряд на «авто» ушёл бы туда параллельно. Теперь процессы, запущенные самим пультом, в учёт не попадают.
Живой отказ: наряд с явным main, пока работа шла на втором аккаунте, получил на боевом пульте 409 с перечнем живых сессий. Стор нарядов до и после совпал байт в байт. После выкатки пульт 30 секунд опрашивали сразу после рестарта: 168 срезов, ни одного с работой на main, хотя замер лимитов прошёл внутри окна.
Что пока не так, как задумано
- Лимиты обновляются раз в 15 минут, поэтому между снимками аккаунт может уйти за порог незаметно для пульта. Сессия, которая упрётся в лимит посреди работы, на другой аккаунт сама не переедет.
- Перевод всех живых сессий на другой аккаунт одной кнопкой не сделан, задача ещё не начата. Пока переносят по одной, нарядом.
- Судья авто-наряда — короткий вызов
claude -p— всегда тратит основной аккаунт, даже когда работа живёт на втором. Какой аккаунт ему брать, решается задачей CP-1043. - После перезапуска пульта замер лимитов, начатый прежним процессом, ещё несколько секунд считается работой: список служебных процессов живёт в памяти сервера.