Самостоятельно размещаемые окружения находятся в публичной бета-версии на планах Team и Enterprise; раздел Доступность и ограничения охватывает путь включения. Эта страница охватывает запуск флота в production; см. быстрый старт для вашего первого runner и сеанса.
Усиление безопасности развёртывания
Самостоятельно размещаемый runner выполняет произвольный, направленный моделью код на вашей инфраструктуре от имени всех, кто может отправить сессию в его окружение. Это любой член вашей организации Anthropic и любой, кто может запустить сессию канала Claude Tag в области действия, которую Owner направил в окружение. Проработайте каждый элемент перед подключением окружения к production-системам:-
Эфемерные контейнеры для каждой сессии: запускайте каждый процесс runner в свежем контейнере или VM, который уничтожается при выходе процесса, с
--capacity 1и значением по умолчанию--drain-grace-sec 0, чтобы каждый контейнер обслуживал ровно одну сессию. При более высокой ёмкости или положительном периоде осушения один контейнер обслуживает несколько сессий от одного заблокированного владельца; см. Runner lifecycle. Не переиспользуйте файловую систему между перезапусками runner, кроме намеренной настройки pre-warmed checkout, и никогда между владельцами.- Когда runner останавливает сессию, он не отправляет никакого сигнала процессу, который продолжает работать после завершения его shell-команды, например сервису, ставшему демоном. Уничтожение контейнера или VM завершает такой процесс.
-
Нет широких учётных данных в образе: не включайте долгоживущие SSH-ключи, учётные данные облачного провайдера или личные токены доступа, которые предоставляют больше, чем нужно сессии. Создавайте учётные данные, используемые во время сессии, такие как токены push или API, для каждой сессии из вашего скрипта-обёртки. Начальное клонирование происходит перед запуском обёртки, поэтому обрабатывайте его с помощью хука жизненного цикла
checkoutили с помощью--use-anthropic-git-proxy, если все репозитории сессии находятся на github.com. Для обоих вариантов см. Configure git. -
Держите учётные данные GitHub хоста вдали от сессий: Claude может использовать любые учётные данные GitHub, которые может прочитать сессия, с любым доступом, который эти учётные данные предоставляют. Не допускайте, чтобы собственные учётные данные GitHub хоста runner с широкими правами попадали туда, где их может прочитать сессия. Такими учётными данными может быть личный токен доступа, токен, который
gh auth loginсохраняет для вашей учётной записи, илиGH_TOKENв окружении runner.- С Anthropic-managed git: при наличии таких учётных данных Claude обращается к GitHub напрямую, а не через Anthropic-managed git.
- Без Anthropic-managed git: учётные данные для клонирования могут оставаться в образе, если вы ограничите их так строго, как описано в Ship git config in your image.
- Держите секрет окружения вдали от хостов, запускающих сессии: секрет окружения может регистрировать runners и получать любую сессию, поставленную в очередь в окружение. На фиксированном флоте он находится на каждом хосте runner, где код любой сессии может прочитать файл секрета. Предпочитайте on-demand runners, где секрет остаётся на хосте оркестратора, который никогда не запускает пользовательский код, и каждый runner получает одноразовый наряд на работу, который регистрирует ровно один runner. На фиксированном флоте рассматривайте файл environment-secret как читаемый каждой сессией и ротируйте секрет после любого подозрения на компрометацию сессии.
- Исходящий трафик по умолчанию запрещён: ограничьте исходящий трафик контейнеров runner и сессии на границе вашей собственной сети в каждом окружении; Default-deny egress описывает, что разрешить и почему.
- Наименьшие привилегии IAM хоста: вычислительная идентификация, прикреплённая к хосту runner, такая как профиль экземпляра или учётная запись сервиса узла, должна предоставлять только то, что нужно самому runner. Сессии должны получать свои собственные учётные данные через ваш скрипт-обёртку, а не наследовать учётные данные хоста.
-
Блокируйте сессиям доступ к эндпоинту облачных метаданных: чтобы сессии не использовали идентификацию хоста, требуется блокировать их доступ к эндпоинту облачных метаданных, а политики исходящего трафика на уровне подсети не перехватывают трафик метаданных link-local, поэтому блокируйте его в самом контейнере:
- IMDSv2 с пределом хопов в один
- GKE Workload Identity со скрытием метаданных
- Явный запрет для
169.254.169.254в сетевом пространстве имён контейнера сессии
-
Изоляция файловой системы для каждого runner: каждый процесс runner получает свой собственный рабочий каталог, который никакой другой процесс на хосте не может читать или записывать. Сделайте
--hooks-dir, скрипт-обёртку и~/.claude/хоста доступными сессии только для чтения, либо встроив их в образ, либо смонтировав только для чтения. -
Dispatch не имеет контроля доступа на уровне окружения: любой член вашей организации Anthropic может отправить сессию в любое из её окружений. Если Owner направляет каналы Claude Tag в окружение, любой, кого допускает настройка доступа Claude Tag, может запускать сессии канала, которые работают там. По умолчанию это любой участник подключённого рабочего пространства Slack, с учётной записью Claude или без неё. Рассматривайте каждый хост runner как доступный для выполнения кода всеми, кто может отправлять в него сессии, и размещайте на хосте runner только данные и учётные данные, которые все эти люди имеют право читать.
--lock-to-accountограничивает, сессии какой учётной записи выполняет данный хост, но не сужает круг тех, кто может отправлять сессии в окружение. Чтобы сделать самостоятельно размещаемые окружения единственным вариантом выбора, Owner может скрыть окружения, размещаемые Anthropic, для всей организации на странице Cloud environments. -
Применяйте защиту repo-settings: выберите режим защиты с помощью
--confine-repo-settings. Режим по умолчаниюwarnзаписывает нарушение в лог и всё равно запускает сессию,enforceотклоняет сессию, аoffотключает сканирование. Runner сканирует закоммиченные настройки каждого репозитория на наличие:- Гранта, который разрешается за пределами собственного рабочего пространства этой сессии: записи
additionalDirectories, правилаEdit,WriteилиNotebookEditвpermissions.allowили записиsandbox.filesystem.allowWriteилиallowRead - Непустого блока
env - Переопределения позиции оператора, такого как
sandbox.enabled: false
--trust-workspaceи не охватывает хуки репозитория,.mcp.jsonили правила Bash; см. Permissions and tool approval, чтобы узнать, где должны находиться эти гранты. - Гранта, который разрешается за пределами собственного рабочего пространства этой сессии: записи
Если в вашей организации включён список разрешённых IP-адресов, добавьте публичные адреса исходящего трафика ваших runners и контейнеров сессий в список разрешённых перед их запуском. Если вы используете on-demand runners, добавьте также адрес хоста оркестратора. Не полагайтесь на список разрешённых как на сетевой контроль для трафика runner или сессий. Вместо этого применяйте default-deny egress на границе вашей собственной сети.
Требования к сети
Runner и порожденные им дочерние сеансы устанавливают исходящие соединения с хостами ниже. Ограничьте исходящий трафик контейнера сеанса этими хостами и конкретными внутренними сервисами, которые сеансам нужно достичь; Default-deny egress охватывает как и почему. Эти хосты всегда требуются:
Runner, использующий
--use-anthropic-git-proxy, направляет свой git-трафик к github.com через api.anthropic.com, поэтому ему не нужен путь к git-хосту github.com. Этот путь всё ещё нужен, если вы устанавливаете --push-outcome-on-release или выполняете push из хука post-session.
Требуются ли эти хосты, зависит от вашей конфигурации:
Эти хосты не нужно добавлять в список разрешённых хостов для трафика runner или сессий:
statsig.anthropic.com,*.sentry.io,claude.aiиplatform.claude.com: эти хосты встречаются в некоторых старых контрольных списках корпоративной сети, но runner к ним не обращается. Получение флагов функций идёт наapi.anthropic.com, а runner аутентифицируется с помощью секрета окружения, а не интерактивного OAuth.mcp-proxy.anthropic.com: самостоятельно размещаемые сессии его не используют. Когда доставка коннекторов включена для вашей организации, коннекторы claude.ai вашей организации попадают в сессии черезapi.anthropic.com. См. MCP servers.
claude.ai, поэтому запускайте их с хоста, чей исходящий трафик это позволяет, а не расширяйте исходящий трафик контейнера сессии:
- Однострочный установщик: получает
install.shсclaude.aiво время установки. - Интерактивный
claude auth login: выполняет вход черезclaude.ai,claude.comиplatform.claude.com. Его используют guided setup, режимdoctorс входом и CI dispatch. Браузер, в котором вы входите, также загружает браузерные проверки страницы входа claude.ai сhcaptcha.com,*.hcaptcha.comиchallenges.cloudflare.com. Если ваша организация использует единый вход (SSO), этот браузер также может быть перенаправлен черезsso.claude.com.
Default-deny egress
Развертывайте контейнеры runner и сеанса в сегменте сети или пространстве имен, чей исходящий трафик ограничен хостами в таблице требований к сети, вашим git-хостом и конкретными внутренними сервисами, которые сеансам нужно достичь. Продукт не может проверить или применить это, поэтому применяйте это на границе вашей собственной сети в каждом окружении. Код сеанса направлен моделью и может попытаться подключиться к произвольным хостам; default-deny egress на сетевом уровне ограничивает, где эти попытки могут приземлиться. Это применяется независимо от режима разрешений: набор инструментов, одобренный по умолчанию, уже включаетBash, поэтому исходящий трафик shell работает без подсказки даже без auto mode.
Для деталей о том, какую телеметрию излучает каждый сеанс и как ее отключить, см. Telemetry.
Аутентификация на исходящий прокси
Некоторые корпоративные исходящие прокси требуют заголовокProxy-Authorization на каждом соединении. Токен в этом заголовке часто ротируется слишком быстро, чтобы писать в URL прокси, который вы устанавливаете в HTTPS_PROXY. Установите HTTPS_PROXY или HTTP_PROXY на URL вашего прокси как обычно, затем установите --proxy-authorization-command или --proxy-authorization-file, чтобы сказать runner, где читать значение заголовка. Оба флага требуют Claude Code v2.1.238 или позже.
Выберите, откуда берется значение Proxy-Authorization
Выберите флаг, который соответствует тому, как вы производите токен Proxy-Authorization:
--proxy-authorization-command <command>: выберите это для токена, который вы генерируете по требованию. Runner запускает команду shell и использует ее обрезанный stdout как значение заголовка, напримерBearer <token>.--proxy-authorization-file <path>: выберите это для токена, который другой процесс ротирует на месте. Runner читает файл и использует его обрезанное содержимое как значение заголовка.
Конфигурации, с которыми runner отказывается запускаться
Каждый флаг также имеет форму переменной окружения, указанную рядом с ним в справочнике флагов CLI runner. Перед тем как runner свяжется с вашим прокси или плоскостью управления, он проверяет флаги и их переменные и отказывается запускаться в трех случаях:- Оба флага установлены: один флаг плюс переменная окружения другого флага считается установкой обоих.
- Нет URL прокси: ни
HTTPS_PROXYниHTTP_PROXYне содержат URLhttp://илиhttps://. Runner читает обе переменные в верхнем или нижнем регистре и не консультируетALL_PROXY. - Любой флаг передан подкоманде orchestrator:
self-hosted-runner orchestratorне принимает флаги или их переменные окружения. Вместо этого передайте флаг каждому runner, который запускает оркестратор.
Что runner изменяет, пока установлен флаг proxy-authorization
С установленным любым флагом runner запускает собственный слушатель и отправляет трафик прокси от себя, своих lifecycle hooks и своих сеансов через этот слушатель. Слушатель добавляет заголовокProxy-Authorization на пути к вашему прокси.
- Слушатель: слушатель является forward proxy на
127.0.0.1. Runner запускает слушатель перед регистрацией в плоскости управления и выходит при запуске, если слушатель не может запуститься. - Переменные прокси: runner переписывает любой из
HTTPS_PROXYиHTTP_PROXY, который вы установили, чтобы он указывал на слушатель. Это переписанное значение достигает самого runner, его lifecycle hooks и каждого сеанса, который он запускает. - Ротация токена: ротированный токен вступает в силу без перезагрузки. Для каждого соединения, которое слушатель открывает для вашего прокси, runner запускает вашу команду или читает ваш файл снова и добавляет результат как заголовок.
- Окружение сеанса: сеанс достигает вашего прокси только через слушатель. В окружении каждого сеанса runner удаляет
ALL_PROXY, удаляет любое написаниеHTTPS_PROXYилиHTTP_PROXY, которое вы не установили, и закрепляетNO_PROXYна собственное значение runner. - Логи: runner никогда не регистрирует значение заголовка.
Настройка git
Раннер управляет извлечением репозиториев, но по умолчанию не настраивает идентичность git и учётные данные. Вы контролируете образ раннера и окружение его процесса, а значит, и конфигурацию git. Выберите один из двух подходов:- Позволить раннеру настроить git: запустите раннер с
--configure-git, чтобы он записал ту же конфигурацию идентичности и подписи коммитов, что используется в сессиях, размещённых Anthropic - Поставлять конфигурацию git в образе: задайте идентичность и учётные данные для push самостоятельно, например чтобы делать коммиты от имени собственного бота
--use-anthropic-git-proxy или задать CLAUDE_RUNNER_USE_GIT_PROXY=1, чтобы попросить Anthropic обслуживать git для сессий раннера.
Минимальные версии Git на хосте раннера: подпись коммитов по SSH с --configure-git требует Git 2.34 или новее, --use-anthropic-git-proxy требует 2.32 или новее, а возобновление сессий из веток, отправленных с помощью --push-outcome-on-release, требует 2.29 или новее. Git 2.24 достаточно, если вы не используете ни одну из этих трёх возможностей и управляете идентичностью git самостоятельно.
Позволить раннеру настроить git
Запустите раннер с--configure-git или задайте SELF_HOSTED_RUNNER_CONFIGURE_GIT=1, чтобы при запуске он записал глобальную конфигурацию git:
user.name = Claudeиuser.email = noreply@anthropic.com, как в сессиях, размещённых Anthropic- Подпись коммитов и тегов в формате SSH через управляемую раннером прослойку, которая подписывает каждый коммит через сервис подписи Anthropic с использованием собственных учётных данных сессии. Подписи проверяются на GitHub по опубликованному ключу подписи SSH от Anthropic.
push.negotiate = true, чтобы перед упаковкой push git спрашивал у вашего git-хоста, какие коммиты у него уже есть. Требуется Claude Code v2.1.257 или новее.core.hooksPath, указывающий на управляемый раннером каталог хуков. Его хукиcommit-msgиprepare-commit-msgдобавляют к каждому коммиту трейлерCo-authored-by:для создателя сессии. Трейлер формируется из адреса электронной почты вCCR_SESSION_ACCOUNT_EMAILи опускается, если эта переменная не задана. Если ваш образ уже задаётcore.hooksPathи раннер не использует управляемый Anthropic git, раннер сохраняет вашу настройку, пропускает установку этих хуков и выводит предупреждение[runner:git].
checkout или post-session, также подписываются от имени сессии, но без трейлера Co-authored-by:. В разделе Конфигурация git внутри хуков жизненного цикла описаны настройки git, которые раннер фиксирует внутри этих хуков.
С --configure-git или без него Claude Code указывает Claude завершать сообщения коммитов трейлером Claude-Session: <url>, а описания pull request — URL сессии. Чтобы исключить и то и другое, задайте attribution.sessionUrl значение false в файле ~/.claude/settings.json на хосте раннера, а затем перезапустите раннер.
Поставлять конфигурацию git в образе
Идентичность git необходима для любого коммита. Задайте её на уровне системы в Dockerfile, чтобы конфигурация применялась независимо от того, от имени какого пользователя работает процесс раннера:git commit завершается ошибкой Please tell me who you are, и сессии не могут продвинуться. Вместо этого можно использовать идентичность собственного бота; раннер не переопределяет эти значения.
Не встраивайте долгоживущие учётные данные для push или учётные данные с широкими правами в общий образ раннера: учётные данные в образе доступны каждой сессии, которую запускает этот образ, кто бы её ни создал. Вместо этого выпускайте для каждой сессии краткоживущий токен с минимально необходимыми правами из своего скрипта-обёртки, используя идентичность создателя сессии, декодированную из JWT сессии. Сочетайте это с эфемерным контейнером для каждой сессии, что требует --capacity 1, чтобы никакие учётные данные не пережили сессию, для которой они были выпущены; см. раздел об усилении защиты.
Если необходимо настроить учётные данные для push на уровне образа, например для ключа развёртывания только для чтения, ограничьте их настолько жёстко, насколько позволяет ваш git-хост:
- Ключ развёртывания SSH, ограниченный одним репозиторием, с перезаписью
url.<base>.insteadOf credential.helper, возвращающий токен с минимальной областью действияGIT_SSH_COMMAND, указывающий на ключ с узкой областью действия
- Раннер задаёт
GIT_TERMINAL_PROMPT=0, поэтому git не запрашивает имя пользователя или пароль. - Раннер запускает SSH с
BatchMode=yes, добавленным к вашемуGIT_SSH_COMMAND, если вы его задали, поэтому SSH не запрашивает парольную фразу или подтверждение хоста. - Раннер задаёт
GCM_INTERACTIVE=never, поэтому Git Credential Manager не открывает диалог входа. - Раннер очищает
core.askPass, поэтому, если вы используете askpass-помощник, задайте его через переменную окруженияGIT_ASKPASS.
GIT_SSH_COMMAND или GIT_ASKPASS, там, куда сессии не могут писать, — так же, как контрольный список усиления защиты требует для каталога хуков и скрипта-обёртки. То же относится к любому ключу или файлу в командной строке этой программы. Собственный git раннера запускает эту программу при клонировании или fetch.
Если каталоги извлечения принадлежат другому uid, чем процесс раннера, git отказывается с ними работать; добавьте safe.directory:
Использование git-прокси Anthropic
С git-прокси Anthropic, также называемым управляемым Anthropic git, образу раннера не нужны ключи SSH, помощник учётных данных,.netrc или другие учётные данные git для самой сессии. Вместо этого раннер просит Anthropic обслуживать git для своих сессий. Для пользовательской сессии, которую обслуживает Anthropic, клонирование раннера и собственные fetch и push сессии проходят через Anthropic, которая использует OAuth-токен GitHub, сохранённый для создателя сессии. Сессии ботов и агентов описаны в разделе Как Anthropic обслуживает git для сессии.
Git-прокси отключён, пока вы не включите его. Раннеру, который обращается к вашему git-хосту со своими учётными данными, он не нужен, и его git работает с любым git-хостом.
Взамен git-прокси ограничивает то, что поддерживает раннер, и меняет то, что ему нужно:
- Только github.com: Anthropic обслуживает сессию, только если все её репозитории находятся на github.com, а git-прокси пока не поддерживает GitHub Enterprise Server. На раннере с git-прокси сессия с репозиторием на другом git-хосте не запускается.
- Учётные данные только для репозиториев сессии: Anthropic предоставляет учётные данные git для репозиториев, входящих в сессию, но не для других репозиториев на том же git-хосте. Приватный подмодуль, зависимость, которую ваш менеджер пакетов получает через git, или маркетплейс плагинов в другом репозитории не получают учётных данных от Anthropic. Попросите тех, кто создаёт сессии, добавлять каждый репозиторий, который нужен сессии, при её создании. Каждый запуск облачной запланированной задачи — это сессия с репозиториями задачи, поэтому попросите владельца каждой задачи добавить все репозитории, которые нужны её запускам.
- Только push веток: push, удаляющий ветку, завершается ошибкой, как и push в ref любого другого типа, например в тег. О том, какие ветки может обновлять push, см. GitHub-прокси.
- Подключённые аккаунты GitHub: попросите каждого, кто создаёт пользовательские сессии с репозиторием, подключить GitHub. Без собственного подключения такие сессии не запускаются.
-
--capacity 1: git-прокси требует одну сессию на процесс раннера, поэтому для параллелизма запускайте больше реплик. Требования перечислены в разделе Включение git-прокси Anthropic. - Заменённая глобальная конфигурация git: раннер удаляет и заменяет глобальную конфигурацию git пользователя, от имени которого работает. Запускайте его от имени выделенного пользователя или в контейнере.
-
Учётные данные хоста для push с хоста: push раннера через
--push-outcome-on-releaseи любой push, который делает ваш хукpost-session, по-прежнему используют собственные учётные данные git хоста раннера и его сетевой путь кgithub.com. Не храните эти учётные данные в глобальной конфигурации git; способы их предоставления см. в разделе Поставлять конфигурацию git в образе. - Решение для каждой сессии: Anthropic решает для каждой сессии на раннере, обслуживать ли её git, и сессия, которую она не обслуживает, не запускается. Причины описаны в разделе Когда сессии не запускаются на раннере с git-прокси.
safe.directory, в системной конфигурации git.
Включение git-прокси Anthropic
Прежде чем запускать раннер с--use-anthropic-git-proxy, убедитесь, что хост раннера соответствует каждому из этих требований. Раннер отказывается запускаться, если требование к capacity или git не выполнено:
- Claude Code v2.1.267 или новее: более ранние версии принимают флаг, но не сообщают запрос на обслуживание git Anthropic и не выводят строку
Registering as opted in, поэтому Anthropic не обслуживает их сессии. --capacity 1, значение по умолчанию: каждый процесс раннера обрабатывает одну сессию за раз, поэтому для параллелизма запускайте больше реплик.- Git 2.32 или новее: более старый git игнорирует посессионную конфигурацию git, которую раннер настраивает для git-прокси.
--use-anthropic-git-proxy в команду раннера или задайте CLAUDE_RUNNER_USE_GIT_PROXY=1 в окружении раннера. Эта команда, выполненная в оболочке на хосте раннера, запускает раннер из быстрого старта с включённым git-прокси:
Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). Затем Anthropic решает для каждой сессии на этом раннере, обслуживать ли её git. Для каждой обслуживаемой сессии раннер записывает в лог строку [runner:session], содержащую governed git ACTIVE. Если вместо этого сессия не запускается, см. Когда сессии не запускаются на раннере с git-прокси.
Как Anthropic обслуживает git для сессии
Для сессии, которую обслуживает Anthropic, клонирование раннера и собственные fetch и push сессии проходят через Anthropic с аутентификацией собственным краткоживущим токеном сессии:- Пользовательские сессии: Anthropic использует OAuth-токен GitHub, сохранённый для создателя сессии.
- Сессии ботов и агентов: Anthropic использует токен установки GitHub App вашей организации.
- Перезапись URL:
--git-host-rewriteи--git-ssh-rewriteне действуют на репозиторий, который обслуживает git-прокси.
Когда сессии не запускаются на раннере с git-прокси
На раннере, запущенном с--use-anthropic-git-proxy, сессия не запускается, если Anthropic не обслуживает её git. Найдите в логе раннера ошибку git, в которой указан адрес api.anthropic.com, содержащий /git_proxy/.
Для каждой сессии раннер на Claude Code v2.1.267 или новее также записывает в лог либо строку [runner:session], содержащую governed git ACTIVE, если Anthropic обслуживает git сессии, либо одну строку [runner:warn], содержащую the server withheld Anthropic-managed git for this session, если нет. Найдите среди этих случаев строку, которую вы видите:
- Нет ни
governed git ACTIVE, ни строкиwithheld: раннер старее Claude Code v2.1.267 не записывает ни одну из этих строк, и Anthropic не обслуживает его сессии. Обновите раннер до v2.1.267 или новее, следуя разделу Закрепление версии. - Строка
withheld: Anthropic не стала обслуживать сессию. Раннер, который раньше работал с git-прокси, может так отказать без каких-либо изменений с вашей стороны.- Репозиторий не на github.com: сессия хотя бы с одним репозиторием на другом git-хосте, например GitHub Enterprise Server, не обслуживается, включая её репозитории на github.com. Отключите git-прокси Anthropic для раннеров этого окружения.
- Все репозитории на github.com: сообщите о сбое вашей команде по работе с аккаунтом Anthropic, указав ID сессии из строки
withheld. Anthropic фиксирует причину на своей стороне.
- Строка, содержащая
remote: access denied by the git proxy: сессии, которую обслуживает Anthropic, всё равно может быть отказано, например когда политика организации запрещает доступ к git для сессии или сессия не авторизована для репозитория. Тогда в логе раннера появляется строка, содержащаяremote: access denied by the git proxy, а остальная часть этой строки объясняет причину. GitHub authentication required: появляется, когда у создателя сессии нет рабочего подключения GitHub на claude.ai. Клонирование сессии завершается ошибкой, и ошибка git содержитGitHub authentication required. Please reconnect your GitHub account.Попросите этого человека подключить или переподключить GitHub в настройках claude.ai.
Отключение git-прокси Anthropic
Если сессии в окружении используют репозиторий на git-хосте, отличном от github.com, например GitHub Enterprise Server, отключите--use-anthropic-git-proxy для раннеров этого окружения.
1
Удалите флаг
Удалите
--use-anthropic-git-proxy из команды раннера. Если вы задали CLAUDE_RUNNER_USE_GIT_PROXY в окружении раннера, например в спецификации pod или файле Compose, удалите её оттуда. В оболочке сбросьте её:2
Предоставьте раннеру учётные данные git
Предоставьте учётные данные, работающие без интерактивного запроса, для каждого git-хоста, который используют сессии раннеров, включая github.com. Любые учётные данные, которые были в глобальной конфигурации git пользователя раннера, утрачены, поскольку раннер удалил эту конфигурацию, пока был задан
--use-anthropic-git-proxy. Поставляйте учётные данные в образе или используйте хук жизненного цикла checkout.3
Откройте сетевой путь
Разрешите раннеру доступ к каждому git-хосту, который используют сессии раннеров, по порту 443 или 22. См. строку о git-хосте в разделе Сетевые требования.
4
Перезапустите раннеры
Перезапустите раннеры, чтобы они зарегистрировались без git-прокси. Затем снова запустите каждую сессию, которая не запустилась.
Доступ к API GitHub без GitHub CLI
Если ваш образ раннера не включает GitHub CLI, Claude Code может предоставить встроенныйgh, чтобы Claude всё равно мог открывать pull request, оставлять комментарии и читать результаты CI. Встроенный gh предназначен для раннеров, использующих управляемый Anthropic git. Он поддерживает одну команду, gh api, которая вызывает REST API GitHub. Требуется Claude Code v2.1.287 или новее в образе раннера.
Эта команда открывает pull request вместо gh pr create. Встроенный gh подставляет {owner} и {repo} для текущего репозитория:
- Учётные данные: встроенный
ghотправляет свои REST-запросы через управляемый Anthropic git, который предоставляет учётные данные GitHub на стороне Anthropic, поэтому образу не нужен токен GitHub для этого - Какие сессии его получают: Anthropic решает для каждой сессии, обслуживает ли управляемый Anthropic git
ghэтой сессии. Если да, строка[runner:session] governed git ACTIVE, которую раннер записывает в лог для сессии, показываетgh_path_shim=true. Если нет, у сессии нетgh jq: установитеjqв образ, если хотите, чтобы работал--jqCLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: если окружение сессии задаёт эту переменную, Claude Code не предоставляет встроенныйgh, и у сессии нетgh
Доверие частному центру сертификации при использовании управляемого Anthropic git
Этот раздел применим, если вы задаётеGIT_SSL_CAINFO или GIT_SSL_NO_VERIFY в окружении раннера, сессии которого используют управляемый Anthropic git. Описанная обработка требует, чтобы раннер работал на Claude Code v2.1.283 или новее.
Когда git на раннере должен доверять частному центру сертификации (CA), например тому, которым подписывает прокси с инспекцией TLS, обычные подходы работают следующим образом:
- Системное хранилище сертификатов: установите свой CA в системное хранилище сертификатов хоста раннера, и git будет доверять ему без каких-либо из этих переменных.
GIT_SSL_CAINFO: задайте путь к PEM-файлу с вашими CA, напримерGIT_SSL_CAINFO=/etc/ssl/corp-ca.pem.GIT_SSL_NO_VERIFY: не помогает за повторно подписывающим прокси. Собственное клонирование раннера через управляемый Anthropic git проверяет сертификаты, даже если эта переменная задана, поэтому такое клонирование завершается ошибкой, пока git не начнёт доверять вашему CA одним из двух других способов.
command запускается с окружением сессии, поэтому получает то же, что и git внутри сессии:
GIT_SSL_CAINFO: то, с чем git сверяет управляемый Anthropic git, зависит от того, где запускается git:- Собственные клонирование и fetch раннера: выполняются без переменной и сверяют управляемый Anthropic git с посессионным файлом сертификатов, который записывает раннер. Раннер собирает этот файл из системного пакета CA хоста раннера и сертификатов из вашего файла.
- Git внутри сессии: получает конфигурацию
http.sslCAInfoс указанием вашего файла вместо переменной, а также записиhttp.<url>.sslCAInfo, которые сверяют управляемый Anthropic git с посессионным файлом. - Хуки
checkoutиpost-session: наследуют переменную без изменений.
GIT_SSL_NO_VERIFY: какие проверки сертификатов остаются отключёнными, зависит от того, где запускается git:- Собственные клонирование и fetch раннера: выполняются без переменной и проверяют предъявленный им сертификат.
- Git внутри сессии: получает конфигурацию
http.sslVerify=falseвместо переменной, поэтому проверки остаются отключёнными для других хостов. Он также получает записиhttp.<url>.sslVerify=true, которые сохраняют проверки включёнными для управляемого Anthropic git. - Хуки
checkoutиpost-session: если у сессии есть репозиторий на управляемом Anthropic git, получают конфигурациюhttp.sslVerify=falseвместо переменной. Они также получают записиhttp.<url>.sslVerify=true, которые сохраняют проверки включёнными для управляемого Anthropic git.
/etc/ssl/certs/ca-certificates.crt или /etc/pki/tls/certs/ca-bundle.crt на хосте раннера. Также нужен файл GIT_SSL_CAINFO, который может прочитать пользователь раннера, который содержит PEM-блоки CERTIFICATE и размер которого не превышает 1 МиБ. Если раннер не может собрать посессионный файл, он записывает в лог строку [runner:warn], содержащую did not build the certificate file и причину. Тогда git использует ваш файл как есть для управляемого Anthropic git. Исправьте то, что указано в строке.
Для каждой сессии, использующей управляемый Anthropic git, раннер также записывает в лог строку [runner:warn], начинающуюся с governed git: GIT_SSL_CAINFO is set или governed git: GIT_SSL_NO_VERIFY is set. В строке сказано, что раннер сделал с этой переменной для собственного git, для git внутри сессии и для ваших хуков жизненного цикла. Она завершается указанием, нужно ли вам что-либо менять.
Добавление репозиториев github.com в сессии каналов Claude Tag
Если Claude не может добавить репозиторий github.com в сессию канала Claude Tag, убедитесь, что у канала есть к нему доступ, даже если ваш раннер может его клонировать. Этот доступ определяется настройками на GitHub и в вашей организации Claude, а не учётными данными git вашего раннера.1
Установите Claude GitHub App
Установите Claude GitHub App в организацию или аккаунт GitHub, которому принадлежит репозиторий, и включите этот репозиторий в установку.
2
Свяжите организацию или аккаунт GitHub
В разделе Organization settings > Git providers свяжите эту организацию или аккаунт со своей организацией Claude. Связать её может Owner вашей организации Claude, который также является владельцем организации GitHub. См. Связывание организации GitHub.
3
Предоставьте доступ к репозиторию
В настройках администратора Claude Tag предоставьте репозиторий каналу или более широкой области действия, включающей канал: его рабочему пространству Slack или всему Slack. Предоставить его может пользователь с ролью Owner или разрешением Claude Tag Admin. Менеджер канала также может добавить его в свой канал, если его аккаунт GitHub является администратором репозитория. См. Предоставление доступа к репозиторию.
Перезапись URL git для частных сетей
URL репозиториев поступают из control plane в формате HTTPS с именем хоста вашего git-хоста; для GitHub Enterprise это имя хоста, которое вы настроили для интеграции GitHub Enterprise на claude.ai. Два повторяемых флага перезаписывают эти URL перед клонированием:--git-host-rewrite <from>=<to>: для split-horizon DNS, когда Anthropic обращается к вашему git-хосту по внешнему имени хоста, а раннеры должны использовать внутреннее--git-ssh-rewrite <host>: для git-хостов, которые принимают только SSH, или для хоста раннера, у которого есть только ключ SSH. Например, чтобы клонировать репозитории github.com по SSH, добавьте--git-ssh-rewrite github.comв команду раннера. Тогда раннер перезаписываетhttps://github.com/owner/repoвgit@github.com:owner/repo. SSH должен подключаться без интерактивного запроса.
--git-ssh-rewrite внутреннее имя хоста. Для полного контроля над извлечением используйте хук жизненного цикла checkout.
Постройте образ runner
Anthropic не публикует предварительно построенный образ runner. Постройте свой собственный вокруг бинарного файлаclaude, наслаивая любой набор инструментов, который нужен вашим репозиториям: языковые среды выполнения, компиляторы, менеджеры пакетов и MCP sidecars.
Рецепты ниже используют --capacity 4, поэтому один контейнер обслуживает до четырех одновременных сеансов от одного заблокированного владельца. Это не обеспечивает изоляцию контейнера для каждого сеанса в разделе усиления безопасности: перед подключением окружения к production-системам либо запустите рецепты на --capacity 1 с одним контейнером на сеанс, либо используйте on-demand runners, которые также держат секрет окружения вне хостов, запускающих сеансы. Если вы добавите Anthropic git proxy к одному из этих рецептов, также измените --capacity на 1.
Этот Dockerfile является минимальной отправной точкой:
linux-x64 на linux-arm64, если ваши узлы ARM, или на linux-x64-musl или linux-arm64-musl на образе на основе musl, таком как Alpine; см. Alpine Linux setup для дополнительных пакетов, которые нужны образам musl. URL является стандартным местоположением выпуска Claude Code, поэтому вы можете проверить загруженный бинарный файл против подписанного манифеста выпуска, как описано в Binary integrity and code signing. Runner требует версию Claude Code 2.1.224 или позже. Постройте образ, затем отправьте его в ваш реестр и ссылайтесь на него в рецептах ниже:
stable и передает его как аргумент сборки, поэтому запуск той же команды после нового стабильного выпуска перестраивает слой загрузки с более новым бинарным файлом. Чтобы закрепить конкретный выпуск для воспроизводимых сборок, передайте номер версии непосредственно как CLAUDE_CODE_VERSION. Замените stable на latest в URL поиска, когда вам нужен выпуск новее, чем стабильный канал, например, тот, который требует недавно запущенная модель.
Размер CPU и памяти для сеансов
Размер контейнера или хоста runner для сеансов, которые он запускает, а не для самого процесса runner. Runner сам опрашивает работу, подготавливает checkout каждого сеанса, запускает ваши lifecycle hooks и запускает и контролирует процессы сеанса. Нагрузка исходит от сеансов: каждый из них является процессом Claude Code плюс все, что он запускает, такое как сборки, наборы тестов, установки пакетов и MCP servers. Для одного сеанса начните со следующих значений, указанных как запросы и лимиты Kubernetes или эквивалент вашей платформы, и рассматривайте их как отправную точку, а не требование:- Память: запрос и лимит 4 ГиБ каждый, что соответствует минимуму 4 ГБ в системных требованиях Claude Code. Держите их равными, чтобы планировщик учитывал полную память контейнера. Когда контейнер достигает своего лимита памяти, ядро убивает процессы внутри него, что может завершить сеанс в середине задачи.
- CPU: запрос 2 CPU и лимит 4 CPU, чтобы сеанс мог всплеснуть выше запроса во время сборок. Ядро дросселирует контейнер на его лимите CPU, а не убивает процессы в нем, поэтому сеансы на лимите работают медленнее, но продолжают работать.
resources:
--capacity, чтобы ограничить число одновременно запускаемых сессий. Он не распределяет CPU или память между ними, поэтому сессии на одном runner делят CPU и память контейнера. Чтобы потребление памяти одной сессией не приводило к завершению остальных, запускайте одну сессию на runner с собственным лимитом памяти.
-
Один сеанс на runner: дайте каждому контейнеру значения одного сеанса. Используйте эту размер на
--capacity 1, которую раздел усиления безопасности рекомендует, и для on-demand runners, где вы устанавливаете значения на рабочую нагрузку, которую вашspawn-runnerhook отправляет, такую как шаблон pod Kubernetes Job. -
Несколько сеансов на runner: на
--capacityвыше одного, умножьте значения одного сеанса на емкость, потому что до такого количества сеансов может работать в контейнере одновременно. Рецепты Kubernetes и Docker Compose запускают--capacity 4без лимитов CPU или памяти, поэтому добавьте лимиты, размер которых соответствует емкости, которую вы запускаете. -
Несколько контейнеров runner на одном хосте: рассчитывайте размер хоста на сумму capacity всех runner, потому что каждый runner учитывает в
--capacityтолько собственные сессии:- Память: умножьте память одной сессии на эту сумму, затем добавьте то, что потребляют собственные процессы хоста
- CPU: умножьте request CPU одной сессии на эту сумму. Чтобы все сессии могли одновременно превышать request, умножайте вместо этого её limit CPU.
--base-dirна хранилище, которое не монтирует контейнер никакого другого runner, чтобы у каждого runner был собственный рабочий каталог.
Задайте каждой сессии собственный лимит памяти
Чтобы при нехватке памяти одна сессия не приводила к завершению остальных, запускайте каждый runner с--capacity 1 под собственным лимитом памяти, равным памяти одной сессии:
- В контейнере: запускайте один runner на контейнер и задайте лимит памяти контейнера, например
limits.memoryв Kubernetes,--memoryв Docker илиmem_limitв файле Compose. Следите, чтобы сумма лимитов контейнеров была меньше памяти хоста, потому что когда память заканчивается на самом хосте, ядро может завершать процессы в любом месте хоста. - Под systemd без контейнера: задайте
MemoryMaxдля unit-а runner и запускайте один runner на хост, потому что runner, использующие один и тот же--base-dirв одной файловой системе, совместно используют клонированные туда репозитории. Например, при--capacity 1и без хукаcheckoutих сессии в одном репозитории могут одновременно работать в одном и том же checkout. Чтобы запустить несколько runner на одном хосте, выделите каждому контейнер.
--capacity больше единицы, сессии делят один лимит. Когда они его достигают, ядро выбирает, какие процессы завершить, без учёта сессий, поэтому потребление памяти одной сессией может завершить остальные.
В Kubernetes разделить сессии не удастся ни при одном значении настройки kubelet singleProcessOOMKill. Ограничить общую память сессии с помощью ulimit для памяти в вашем скрипте-обёртке тоже не получится. Ядро применяет значение вроде ulimit -v к каждому процессу отдельно.
Оставьте запас под лимитами процессов и потоков
Чтобы подобрать лимит процессов и потоков, измерьте, сколько нужно одной сессии, и умножьте это на число сессий, которые делят лимит. Когда эти сессии достигают лимита, ядро отказывает в создании новых процессов и потоков, поэтому команды, сборки и тесты в любой из них могут завершаться ошибкой. Отклонённый вызов возвращаетEAGAIN, стандартное сообщение которого — Resource temporarily unavailable.
Допустим, одна сессия достигает пика в 300 процессов и потоков, и четыре сессии делят лимит. Вместе им нужно 1 200, а при лимите 2 400 вы оставляете запас для сборок, которым нужно вдвое больше.
Где задать каждый лимит
Каждый лимит в этой таблице учитывает потоки наравне с процессами, аpids.max — это файл, в котором хранится лимит cgroup:
Если вы не задали
TasksMax, systemd применяет DefaultTasksMax, который по умолчанию равен 15% от наименьшего из значений kernel.pid_max, kernel.threads-max и pids.max корневой cgroup.
Измерьте пик одной сессии
Чтобы получить значения для ваших runner, измерьте одну сессию на её хосте, которым в Kubernetes является узел:1
Запустите один runner отдельно
Запустите один runner с
--capacity 1, ничего больше не размещая в его контейнере или unit-е systemd, так чтобы он был один на своём хосте и в своём окружении:- Его хост: используйте хост, на котором не работает ни один другой runner. Тогда на следующем шаге вы сможете найти его процесс.
- Его окружение: сделайте его единственным runner в окружении, например в окружении, которое вы создадите для теста. Сессии направляются в окружение, а не на конкретный runner.
2
Найдите каталог cgroup runner
На хосте, а не внутри контейнера, найдите ID процесса runner, например с помощью
ps. Прочитайте /proc/<pid>/cgroup для этого ID:- Есть строка, содержащая
:pids:: хост использует cgroup v1, а каталог — это/sys/fs/cgroup/pids, за которым следует текст после:pids: - Единственная строка начинается с
0::: хост использует cgroup v2, а каталог — это/sys/fs/cgroup, за которым следует текст после0::
3
Выполните репрезентативную сборку и запишите пик
Проведите одну сессию через репрезентативную сборку и возьмите пиковое значение из каталога cgroup runner:
- В каталоге есть
pids.peak: прочитайте этот файл после завершения сборки - В каталоге нет
pids.peak: читайтеpids.currentраз в секунду во время сборки и сохраняйте наибольшее значение. Пик между двумя чтениями останется незамеченным, поэтому считайте это значение заниженной оценкой.
4
Умножьте на число сессий, делящих лимит
- Pod, контейнер или unit одного runner: умножьте пик на
--capacityэтого runner - Лимит, который делят несколько runner: умножьте пик на сумму их capacity
5
Задайте лимит выше полученного результата
Более высокий лимит оставляет запас для более тяжёлых сборок, а более низкий быстрее останавливает fork-бомбу.
Проверьте лимит pids.max
- Проверьте, был ли достигнут
pids.max: найдите в логе ядра хоста, например с помощьюdmesg, строку, содержащуюcgroup: fork rejected by pids controller inи путь cgroup. Ядро записывает её в лог только в первый раз, когда отказывает в создании процесса или потока в этой cgroup, поэтому ищите по всему логу. - Узнайте действующий лимит: прочитайте
pids.maxв каталоге cgroup runner и в каждом каталоге выше него. Наименьшее значение и есть действующий лимит, аmaxозначает отсутствие лимита на этом уровне.
Kubernetes
Runner обслуживаетGET /healthz на порту 8080 по умолчанию, конфигурируемом с помощью --health-port, поэтому зонды Kubernetes работают без дополнительной настройки. Конечная точка возвращает 200 всякий раз, когда процесс живой, поэтому зонды ниже обнаруживают мертвый процесс, а не застрявший; чтобы поймать runner, который перестал опрашивать, установите оповещение на серию last_poll_age_seconds из /metrics. Развертывание ниже монтирует секрет окружения из Kubernetes Secret, указывает зонды liveness и readiness на /healthz и устанавливает период завершения 90 секунд. См. Shutdown timing для того, почему период завершения имеет значение.
Манифест не устанавливает resources CPU или памяти на контейнер runner. Добавьте блок, размер которого соответствует емкости, которую вы запускаете, как описано в Size CPU and memory for sessions.
claude-runners. Сначала создайте пространство имен:
(umask 077 && cat > ./environment-secret), вставьте секрет, нажмите Enter, затем Ctrl-D. Затем создайте Secret и удалите файл:
Docker Compose
Сервис Compose ниже перезапускает runner всякий раз, когда он выходит, что охватывает как сбои, так и нормальный выход после осушения. Политика перезагрузки Docker перезапускает тот же контейнер с его записываемым слоем нетронутым, поэтому runner возвращается на переиспользованной файловой системе, а не на свежей, которую рекомендует позиция усиления безопасности; используйте этот рецепт для оценки, и для production либо пересоздайте контейнер за запуск, либо используйте оркестратор, который это делает. Docker ждёт дольше перед каждым перезапуском контейнера, который продолжает выходить, вплоть до потолка, поэтому runner, который не может запуститься, не продолжает перезапускаться в плотном цикле под этим рецептом. Когда runner выходит описывает, что проверить, когда это происходит.Время завершения работы
ПослеSIGTERM runner нужно время, чтобы корректно завершить свои сессии, прежде чем ваш оркестратор его убьёт. Сколько именно, он записывает в лог при запуске, в строке, которая при настройках по умолчанию содержит This runner needs up to 80s. Установите таймаут остановки вашего оркестратора не меньше этого количества секунд: terminationGracePeriodSeconds в Kubernetes, stop_grace_period в Docker Compose или эквивалент на вашей платформе. В Kubernetes значение по умолчанию составляет 30 секунд, поэтому без этой настройки он может остановить pod до того, как runner завершит работу.
При получении SIGTERM runner перестаёт принимать новые сессии. Затем он начинает корректное завершение работы, называемое drain, если только вы не установили --defer-shutdown-max-min, чтобы отложить его. Drain состоит из трёх шагов:
- Runner ждёт до
--drain-wait-secсекунд, по умолчанию0, пока завершатся ещё выполняемые ходы. - Он завершает дерево процессов каждой сессии, включая любую команду, которую Claude всё ещё выполнял, но не процесс, продолжающий работать после завершения своей shell-команды.
- Он запускает lifecycle-хук
post-session.
post-session ещё сохраняет незафиксированную работу.
Время, записываемое в лог, является суммой следующих значений:
--drain-wait-secдля шага 1, по умолчанию 0 секунд--session-stop-grace-secдля шага 2, по умолчанию 5 секунд--post-session-hook-timeout-secдля шага 3, по умолчанию 60 секунд- Фиксированный запас в 15 секунд
- Ещё 30 секунд, когда установлен
--push-outcome-on-release
--capacity не увеличивает это время, потому что runner выполняет drain всех своих сессий одновременно.
Выделите больше времени, если вы устанавливаете любой из этих флагов:
- С
--retire-at: оставьте между временем retire и временем остановки хоста достаточно времени для завершения типичных ходов, плюс ожидание фоновых задач, которое описывает Runner lifecycle, плюс время, записанное в лог. Вычисляйте время retire при каждом запуске, напримерdate +%sплюс предполагаемое время жизни runner. - С
--defer-shutdown-max-min: таймаут остановки также должен охватывать настроенные вами минуты и дополнительное ожидание перед началом drain, 75 секунд при настройках по умолчанию. Оба значения объясняются в разделе Defer the drain past the first signal. Runner также записывает это более длительное время в лог при запуске.
Дайте выполняемым ходам завершиться перед перезапуском
Чтобы дать выполняемым ходам завершиться перед rolling restart, установите--drain-wait-sec и увеличьте таймаут остановки как минимум до этого ожидания плюс 80 секунд. При значении ожидания по умолчанию 0 перезапуск прерывает любой ещё выполняемый ход, и сессия возобновляется на другом runner без своей неотправленной работы.
--drain-wait-sec: примерно столько, сколько длятся ходы и фоновые задачи, которым вы хотите дать завершиться. Это максимум: ожидание заканчивается раньше, если они завершатся первыми.- Ваш таймаут остановки:
terminationGracePeriodSecondsв Kubernetes,stop_grace_periodв Docker Compose. Например, при--drain-wait-sec 120замените90в рецепте для Kubernetes или Docker Compose на200. - Если вы изменили другие настройки завершения работы: 80 секунд рассчитаны на настройки по умолчанию. Запустите один runner с вашими флагами и используйте как минимум число из его стартовой строки
This runner needs up to, которое включает ожидание. Если установлен--defer-shutdown-max-min, используйте большее число из следующей стартовой строки, послеSize the stop timeout to at least. В разделе Время завершения работы перечислено, что увеличивает эти числа.
Опубликуйте статусное сообщение перед перезапуском
Перед перезапуском, который прервёт сессии, попросите Owner опубликовать статусное сообщение в среде на странице администратора Cloud environments. На claude.ai/code участники видят это сообщение в своих сессиях в этой среде. Попросите Owner указать срок действия при публикации или удалить сообщение, когда runner снова заработают.Defer the drain past the first signal
Установите--defer-shutdown-max-min <n>, если вы хотите, чтобы runner, который вы перезапускаете, продолжал обслуживать сессии, которые он удерживает, до n минут, вместо того чтобы выполнять их drain при первом сигнале. При первом SIGTERM или SIGINT runner прекращает принимать новую работу и продолжает обслуживать сессии, которые он удерживает. Он продолжает опрашивать, чтобы control plane не переставлял эти сессии. Требует Claude Code v2.1.238 или позже.
Что происходит с сессиями, которые runner удерживает после первого сигнала
На первых двух этапах, следующих за сигналом, runner выпускает сессии, и выпущенная сессия возобновляется на свежем runner, когда её пользователь отправляет следующее сообщение. Отсчитывая от первого сигнала, runner проходит через три этапа:- В течение первых
nминут: runner обслуживает свои сессии нормально и продолжает применять--startup-timeout-minи--kill-session-after-min. Если вы также установили--release-idle-session-min, runner выпускает любую сессию, пользователь которой был неактивен столько времени; без этого неактивные сессии остаются на runner. - Когда истекают
nминут: runner выпускает каждую сессию, которую он всё ещё удерживает, неактивную или нет. Runner ждёт, пока завершится ход сессии, находящейся посреди хода, и ещё до 60 секунд для фоновых задач этого хода, прежде чем выпустить эту сессию. - Когда истекает post-release grace: runner выполняет drain любых сессий, которые он всё ещё удерживает, и control plane сразу же переставляет каждую такую сессию на другой runner. Post-release grace начинается, когда истекают
nминут, и составляет 75 секунд при значениях по умолчанию. Если вы установили--drain-wait-secвыше 60 секунд, post-release grace вместо этого составляет--drain-wait-secплюс 15 секунд.
--defer-shutdown-max-min. Как только drain находится в процессе, следующий сигнал принудительно завершает runner. Это верно независимо от того, начал ли drain второй сигнал или истечение post-release grace.
Установите таймаут остановки
Дайте таймауту остановки вашего хоста по крайней мере сумму трёх частей:n минут, которые вы настраиваете, post-release grace и drain, который описывает раздел Время завершения работы. При настройках по умолчанию post-release grace составляет 75 секунд, а drain занимает до 80 секунд, поэтому выделите n минут плюс 155 секунд. Runner выводит эту сумму при запуске всякий раз, когда установлен --defer-shutdown-max-min.
Если таймаут остановки истекает до того, как runner завершит работу, сессии, которые он всё ещё удерживает, не получают хук post-session. Runner не отменяет регистрацию, поэтому control plane может потребоваться несколько минут, а иногда и больше, чтобы переставить эти сессии. Если вы не можете дать таймауту остановки эту сумму, оставьте --defer-shutdown-max-min неустановленным, чтобы runner вместо этого выполнял drain при первом сигнале.
Что достигает работающего хука post-session
Хукpost-session и дочерний процесс сессии Claude каждый запускаются в своей собственной POSIX-группе процессов, отдельно от runner, поэтому механизмы остановки достигают их по-разному:
SIGTERM, пока runner уже выполняет drain: немедленно принудительно завершает runner, пропуская всё, что остаётся от drain. Без--defer-shutdown-max-minэто второйSIGTERM, который получает runner. Ничто не отправляет сигнал работающему хукуpost-session, поэтому на голом хосте, где init-процесс усыновляет осиротевшие процессы, он завершается самостоятельно, но без надзора: его бюджет таймаута больше не применяется, и запись в закрытый канал логов может убить его сSIGPIPE, поэтому хук, который должен пережить принудительное завершение в такой среде, должен перенаправлять свой собственный вывод в файл. В рецептах контейнеров на этой странице runner является PID 1 контейнера и его выход завершает контейнер, а при используемом systemd по умолчаниюKillMode=control-groupзавершение всей cgroup достигает и хука, как описывает пункт Cgroup-wide kills; в обоих случаях рассматривайте принудительное завершение как фатальное для хука и полагайтесь вместо этого на период grace.- Process-group-wide signals, такие как
kill -- -<pid>в скрипте-обёртке, управление заданиями оболочки или watchdog на уровне группы: достигают runner и подпроцесса, работающего посреди хукаcheckout, который намеренно остаётся привязанным к группе, но не работающего хукаpost-sessionили дочернего процесса сессии. - Cgroup-wide kills, такие как используемый systemd по умолчанию
KillMode=control-groupилиSIGKILL, который Kubernetes доставляет всему контейнеру, когда истекаетterminationGracePeriodSeconds: достигают всего, включая хук. Изоляция группы процессов не защищает от этого, поэтому период grace должен охватывать весь drain. - Собственный таймаут хука: когда хук превышает
--post-session-hook-timeout-sec, runner отправляетSIGTERMна всю группу процессов хука, затемSIGKILLдве секунды спустя, поэтому рабочий процесс, порождённый хуком, такой как tar, rsync или git, завершается вместе с оболочкой-обёрткой, а не выживает как осиротевший процесс. Надзор runner заканчивается, как только stdio хука закрывается: рабочий процесс, который перенаправил свой собственный вывод в файл и пережил этапSIGTERM, находится вне досягаемости runner.
post-session всё ещё работают, поэтому вы можете отличить тихий drain от того, который находится посреди создания снимка.
Держите базовый каталог и емкость идентичными на всех runners
Если runner умирает в середине сеанса, сервер переставляет сеанс в очередь и другой runner в окружении его подхватывает. Этот runner выводит путь checkout из своего собственного--base-dir и --capacity: --capacity 1 проверяет прямо под --base-dir, и --capacity выше 1 использует вместо этого per-session worktrees. Когда runners в одном окружении используют разные значения для любого флага, рабочий каталог возобновленного сеанса изменяется, и абсолютные пути, которые агент записал ранее, в редактированиях, вызовах инструментов или своих собственных заметках, указывают на местоположение, которое больше не существует.
Используйте одинаковые --base-dir и --capacity на каждом runner в окружении и не используйте значение для каждого хоста, такое как ID экземпляра или имя хоста. Передавайте оба флага явно, как это делают рецепты для Kubernetes и Docker Compose, чтобы ни одно из значений не зависело от значения по умолчанию.
Базовый каталог по умолчанию /workspace, с исключением, которое записывает строка справочника --base-dir. Runner нуждается в доступе на запись к нему. При запуске, перед регистрацией, runner создает каталог и подтверждает, что может писать в него, и выходит с cannot create or write to base directory, когда не может. Runner, запущенный как root, создает по умолчанию /workspace сам. Для non-root runner создайте каталог и дайте пользователю runner владение перед запуском runner, или укажите --base-dir на каталог, который этот пользователь уже владеет.
Переиспользуйте pre-warmed checkout
Для больших репозиториев клонирование может занимать бо́льшую часть времени запуска сессии. Чтобы пропустить холодное клонирование, подготовьте клон самостоятельно по тому пути, где runner хранит собственный. Безcheckout хука runner хранит один канонический клон на репозиторий по пути <base-dir>/<repo-owner>/<repo> и переиспользует его между сессиями:
- При
--capacity 1: runner получает запрошенный ref, отсоединяетHEADи выполняет жесткий сброс на него, что происходит почти мгновенно, когда изменилось немногое. - При
--capacityбольше единицы: runner выполняет fetch в этот клон, а затем создает из него отдельный worktree для каждой сессии. Pre-warmed клон экономит загрузку, но не checkout.
- Клон в образе: постройте клон в образ runner на этом пути. Каждый свежий контейнер затем начинается с теплым клоном без переиспользования диска.
- Клон на постоянном томе: на runners, которые вы предварительно блокируете для учетной записи одного пользователя с
--lock-to-account, укажите--base-dirна постоянный том, поэтому диск только когда-либо обслуживает эту учетную запись. Pre-locked runner никогда не подхватывает сеансы канала Claude Tag, поэтому этот вариант не применяется к runners, которые их обслуживают.
-
Глубина клона: создавайте клон без
--depth, с--single-branchили без него. Runner никогда не передает--depthпри выполнении fetch в клон с удаленным репозиториемorigin, даже если вы задалиCLAUDE_RUNNER_FETCH_DEPTH. С полным клоном каждая сессия загружает только те коммиты, которых нет в клоне. С неглубоким клоном сессия в ветке, ответвившейся до самого старого коммита клона, загружает всю историю этой ветки. -
Отслеживаемые изменения сбрасываются, неотслеживаемые файлы сохраняются: при
--capacity 1каждая сессия начинается с жесткого сброса, который стирает отслеживаемые изменения предыдущей сессии, но runner никогда не запускаетgit clean, поэтому неотслеживаемые файлы из более ранних сессий заблокированного владельца остаются в дереве. -
Каталоги для каждого сеанса также сохраняются: рядом с checkout runner создает записи для каждого сеанса под
<base-dir>/_sessions/для каждого сеанса, который он запускает. Локальный каталог конфигурации Claude сеанса содержит локальную копию транскрипта беседы. Рядом с ним находятся загруженные файлы сеанса, когда сеанс их имеет. Каталог сеанса находится там же: он содержит любые worktrees для каждого сеанса иcheckouthook checkouts во время работы сеанса, и он сохраняет все остальное, что Claude написал в нем. По умолчанию runner оставляет их на месте, когда сеанс заканчивается, поэтому на диске, который пережил процесс runner, они накапливаются. Каждый сеанс запускается как собственный пользователь runner, поэтому любой более поздний сеанс, который обслуживает этот диск, может их прочитать. Если вы сохраняете постоянный--base-dir, размер тома для этого роста. То же самое применяется к любой установке, которая перезапускает runner на той же файловой системе, включая Docker Compose рецепт. -
С
--remove-session-state, каталоги для каждого сеанса не сохраняются: запустите runner с--remove-session-state, чтобы он удалял каталоги для каждого сеанса по мере завершения сеанса. Удаление является наилучшей попыткой: каталоги остаются, когда runner убивается до того, как его очистка запустится. Канонический клон и файлы, которые сеанс написал в другом месте на хосте, такие как временный каталог, остаются независимо. -
С git proxy сброс превращается в checkout: с
--use-anthropic-git-proxyrunner очищает.git/клона перед каждой сессией. Он сохраняет хранилище объектов и состояние shallow, а индекс удаляет. Без индекса каждая сессия начинается с полного checkout рабочего дерева вместо почти мгновенного сброса. Runner по-прежнему выполняет fetch в существующий клон, а не клонирует репозиторий заново. Pre-warm подмодулей при использовании прокси не поддерживается. -
С git proxy pre-warm в формате
reftableтеряет свои refs: очищая.git/перед каждой сессией, runner сохраняет.git/refs/и удаляет каталог.git/reftable/. Клон, подготовленный в формате refsreftableв git, при этом теряет свои refs, поэтому выполняйте pre-warm клона в формате refsfiles. - Длинные клоны не нуждаются в обходном пути: runner ограничивает каждую операцию git с помощью 120-секундного наблюдателя без прогресса и 30-минутного жесткого лимита, а не плоского тайм-аута, поэтому медленный холодный клон, который продолжает сообщать о прогрессе, завершается.
Убедитесь, что runner использует ваш клон
Чтобы убедиться, что runner использует ваш клон, запустите его с--log-level debug и начните сессию в репозитории. Runner записывает в лог Prefetched repo found at и путь к клону. Если вместо этого он записывает No prefetched repo, fresh init at, значит, по пути, указанному в этой строке, он не нашел пригодного клона. Проверьте, что ваш клон находится по этому пути, принадлежит пользователю runner и имеет удаленный репозиторий с именем origin.
Если runner находит ваш клон, но сессии по-прежнему запускаются медленно, проверьте, не является ли клон неглубоким.
Закрепите версию
Дочерний процесс каждой сессии по умолчанию запускает собственный бинарный файл runner, и runner отключает auto-update внутри сессий, которые он порождает. При таком поведении по умолчанию каждая сессия запускает версию, которую вы установили на хосте или встроили в образ. Обновление на уровне хоста вступает в силу в следующий раз, когда runner запускается. Выберите, какую версию запускают ваши сессии и когда она меняется:- Перед тем как закрепить версию: проверьте версии Claude Code, которые требуют модели, для каждой модели, которую используют ваши сессии. Если модель требует более новую версию, чем та, которую запускают ваши сессии, сервер отклоняет запросы для этой модели с Claude Code does not support this model.
- Чтобы держать флот на одной версии: постройте образ с закрепленной версией или на голом хосте установите конкретную версию и отключите auto-updates
- Чтобы обновить фиксированный флот: прочитайте записи журнала изменений между вашей версией и той, которую вы устанавливаете, затем установите более новую версию или пересоберите образ и перезапустите runners
- Чтобы обновить runners по требованию: прочитайте записи журнала изменений между вашей версией и той, которую вы устанавливаете, затем измените образ, который запускает ваш хук
spawn-runner. Каждый новый runner получает новую версию. Runner, который уже запущен, включая резервный runner, запущенный--min-idle, сохраняет свою версию до завершения работы. Не перезапускайте его, потому что его рабочий заказ одноразовый. - Если сессии запускают другой бинарный файл: если вы используете
--exec-pathили хукcommand, чтобы запускать бинарный файл Claude Code, отличный от собственного бинарного файла runner, сессии запускают версию этого бинарного файла. Обновляйте этот бинарный файл при обновлении runner. - Плагины: рынки плагинов тоже не auto-update; установите
FORCE_AUTOUPDATE_PLUGINS=1в окружении runner, чтобы позволить плагинам auto-update, пока бинарный файл остается закрепленным
Масштабируйте флот
Ваш оркестратор решает, когда добавлять или удалять runners. Из-за блокировки one-owner-per-runner, минимальное количество реплик - это количество пользователей и агентов Claude Tag, которых вы ожидаете быть активными одновременно;--capacity контролирует параллелизм в пределах сеансов одного владельца, а не на всех владельцах.
Доступны два подхода к масштабированию:
- Фиксированный флот: запустите статический набор реплик runner и масштабируйте на Prometheus metrics, которые обслуживает каждый runner
- On-demand runners: запустите подкоманду
claude self-hosted-runner orchestrator, которая опрашивает Anthropic для сеансов, которые поставлены в очередь без доступного runner и вызывает ваш hookspawn-runnerдля загрузки одного на сеанс. См. On-demand runners.
Освобождение простаивающих сессий во фиксированном флоте
Если сессии остаются в очереди во фиксированном флоте, хотя все runners в сети, причиной могут быть простаивающие сессии. По умолчанию сессия сохраняет свой слот после завершения своего хода, а её runner остаётся заблокированным за владельцем этой сессии. Если облачная запланированная задача вашей команды запускает новую сессию при каждом выполнении, её простаивающие сессии могут занять все слоты. Чтобы проверить это, посмотритеclaude_code_self_hosted_runner_session_idle_seconds в Prometheus metrics каждого runner. У каждой сессии, которую удерживает runner, есть собственный ряд: 0, пока сессия занята, в противном случае — количество секунд с момента перехода в простой. Выберите допустимое время простоя: дольше пауз, которые люди делают посреди задачи, и достаточно короткое, чтобы каждое выполнение освобождалось до начала следующего выполнения запланированной задачи. Сравните ряды с этим временем.
- Ряды выше этого времени, например
1800для 30 минут: простаивающие сессии удерживают эти слоты, поэтому задайте--release-idle-session-min - Все ряды ниже него: ни одна сессия не простаивала так долго. Добавьте runners или задайте
--kill-session-after-minдля сессий, которые никогда не переходят в простой. - Runner без рядов: его не удерживает ни одна сессия. См. Sessions stay queued в разделе Устранение неполадок.
- Runner, метрики которого не отвечают: его процесс мог завершиться. См. An environment that looks healthy в разделе Когда runner завершает работу.
--release-idle-session-min и этим временем в минутах, например --release-idle-session-min 30. Тогда runner освобождает любую сессию, которая простаивает так долго, независимо от того, запустила её запланированная задача или человек.
- Неотправленная работа: освобождённая сессия возобновляется при следующем сообщении, но без работы, которую она не отправила (push). Чтобы сохранить её коммиты, см. Resumed sessions lose unpushed work.
- Завершение runner: runner, освободивший свою последнюю сессию, начинает обычное завершение. Настройте ваш супервизор так, чтобы он перезапускал его на чистой файловой системе, как описано в разделе Усиление защиты развёртывания.
- Сессии, которые никогда не переходят в простой: также задайте
--kill-session-after-min, потому что некоторые сессии не считаются простаивающими
Известные проблемы и ограничения
Ниже приведены ограничения в этом выпуске с обходными путями, где они существуют.Трафик коннектора покидает вашу сеть
Anthropic вызывает инструменты коннектора из своей собственной инфраструктуры, а не из вашего runner. Инструменты коннектора - это коннекторы claude.ai, такие как GitHub, Slack и Linear. Когда Claude использует коннектор в самостоятельно размещаемом сеансе, этот трафик идет черезapi.anthropic.com, а не исходит из границы вашей сети.
Чтобы держать коннектор вне самостоятельно размещаемых сеансов, отфильтруйте его с помощью allowedMcpServers и deniedMcpServers параметров политики. Claude Code применяет эти параметры к коннекторам, которые Anthropic доставляет, а также к серверам, которые вы сеете с хоста runner и серверам, которые пользователи добавляют, поэтому если вы развертываете список разрешений для других серверов, Claude Code блокирует доставленные коннекторы тоже. Чтобы держать коннекторы доступными наряду со списком разрешений на основе URL, добавьте записи, которые соответствуют путям прокси Anthropic для доставленных коннекторов:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
Некоторые сеансы не считаются неактивными
Сеанс, держащий фоновую задачу, которая никогда не заканчивается, не считается неактивным, поэтому--release-idle-session-min не выпустит слот этого сеанса. Сеанс, который ждет одобрения, запрошенного изнутри работающего вызова инструмента, тоже не считается неактивным. Всегда устанавливайте --kill-session-after-min рядом с ним как жесткий упор, чтобы никакой сеанс не мог держать слот неопределенно долго.
--kill-session-after-min является упором для убегающих сеансов. На runner на v2.1.260 или позже, сеанс, который достигает лимита, не завершается сразу. Runner дает ему окно благодати, 15 минут по умолчанию, которое вы можете изменить с помощью SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS:
- Если сеанс ждет своего пользователя, runner выпускает его. Если его ход закончился и он держит только фоновые задачи, runner ждет до 60 секунд, чтобы эти задачи завершились, а затем выпускает его. Сеанс возобновляется, когда его пользователь отправляет свое следующее сообщение.
- Если ход все еще работает, runner ждет, пока ход закончится, или пока сеанс не будет ждать своего пользователя, и затем выпускает его.
- Если сеанс все еще на runner, когда окно благодати истекает, runner завершает его, и любая работа работающего хода потеряна. Ход, ожидающий одобрения, запрошенного изнутри работающего вызова инструмента, - это один способ, которым сеанс переживает окно.
--kill-session-after-min 480 для 8 часов. Чтобы освободить слоты из разговоров, которые становятся неактивными, используйте вместо этого --release-idle-session-min.
Дополнительные ограничения
- Возобновлённые сессии теряют неотправленную работу: новый runner заново клонирует репозиторий с его начальной ветки, поэтому работа, которую сессия не отправила, теряется.
- Чтобы сохранить закоммиченную работу: задайте
--push-outcome-on-releaseна каждом runner в окружении, поскольку runner без этого флага возобновляет сессию с её начальной ветки. Runner с этим флагом перед освобождением по возможности отправляет (push) ветки результатов сессии, и возобновлённая сессия начинается с этих коммитов. Для push используются собственные учётные данные git хоста runner, в том числе на runner, который использует управляемый Anthropic git. - С хуком
checkout: репозитории, извлечённые с помощью хука жизненного циклаcheckout, не отправляются. Вместо этого сохраняйте их снимки из хукаpost-session. - Перед включением флага: ограничьте круг тех, кто может выполнять push в refs
claude/*на исходном удалённом репозитории. При возобновлении runner получает ранее отправленную ветку, не проверяя, кто её отправил. - Незакоммиченные изменения всё равно теряются: чтобы побудить Claude делать коммит и push по завершении ответа, см. Побуждение сессий отправлять свою работу
- Чтобы сохранить закоммиченную работу: задайте
- Команда, прерванная из-за потери runner, может выполниться повторно: когда сессия возобновляется на другом runner, Claude видит прерванный вызов инструмента с результатом, сообщающим, что его исход неизвестен.
- Репозиторий, добавленный в середине сессии, может не клонироваться: Claude клонирует его с помощью
git cloneпо HTTPS. На runner без--use-anthropic-git-proxyклонирование завершается ошибкой аутентификации git, если ничто на хосте не может прочитать репозиторий. По возможности выбирайте все необходимые сессии репозитории при её создании. - Некоторые коннекторы не появляются в самостоятельно размещаемых сеансах: коннектор, который вы еще не подключили в параметрах claude.ai, не указан в самостоятельно размещаемом сеансе, и сеанс не будет вас приглашать подключить его. Подключите его в параметрах сначала, затем запустите свежий сеанс. Добавление коннектора в уже работающий сеанс тоже не делает его инструменты доступными для Claude; запустите свежий сеанс, чтобы подхватить недавно добавленный коннектор.
- Когда кто-то архивирует или удаляет сессию либо освобождает её runner из меню сессии: не рассчитывайте на push через
--push-outcome-on-release. Чтобы сохранить коммиты в этих случаях, отправляйтеHEADв резервную ветку из хукаpost-session. Если вы берёте за основу приведённый там пример, сделайте так, чтобы он выполнял push, даже когда незакоммиченных изменений нет, поскольку он выполняет push только после коммита-снимка.
Сообщите о проблеме
Для проблем с самостоятельно размещаемыми окружениями свяжитесь с командой вашего аккаунта Anthropic.Устранение неполадок
Для пошаговой диагностики запустите подкоманду doctor на хосте runner’а. Подкоманда doctor запускает интерактивную сессию Claude Code с подключёнными логами и состоянием runner’а. Сначала войдите в систему с помощьюclaude auth login на этом хосте, чтобы сессия могла запрашивать данные о вашем окружении, его runner’ах и его сессиях в очереди. Без этого входа, например когда хост проходит аутентификацию с помощью API-ключа, она ограничена локальным эндпоинтом проверки работоспособности, метриками и логом runner’а, причём лог она читает, только если вы запустили runner с --log-file.
-
Runner не появляется в окружении: убедитесь, что хост может подключиться к
api.anthropic.comпо HTTPS, секрет окружения актуален, а часы хоста отклоняются от реального времени не более чем на пять минут; большее расхождение приводит к сбою аутентификации. При сбое аутентификации runner записывает в лог[runner:fatal]с причиной отказа. -
Runner завершается при запуске с
cannot create or write to base directory: runner не может создать--base-dirили записать в него; по умолчанию это/workspace. Исправьте владельца каталога или укажите в--base-dirпуть, доступный для записи, как описано в разделе Сохраняйте одинаковые базовый каталог и ёмкость на всех runner’ах. Если вместо этого runner записывает в лог[runner:fatal]с сообщением о том, что истёк таймаут проверки базового каталога, каталог находится на зависшем монтировании NFS или CSI. Проверьте работоспособность монтирования, а не права доступа. Runner выводит оба этих сбоя запуска в stderr до того, как откроет--log-file, поэтому ищите их в терминале или в логах контейнеров вашей платформы, а не в файле лога. До v2.1.225 runner не проверял базовый каталог при запуске, и такая ошибка конфигурации вместо этого приводила к сбою сессий после их принятия. -
Runner завершается при запуске с
RegisterRunner failed:и ошибкой TLS-сертификата: например, ошибка может бытьself signed certificate in certificate chain. Runner не доверяет центру сертификации (CA), выпустившему полученный сертификат, как бывает за прокси с инспекцией TLS. Задайте в окружении runner’а переменнуюNODE_EXTRA_CA_CERTSс путём к PEM-файлу, содержащему корневой CA прокси. Для сессий её не нужно задавать повторно — они её наследуют. Git её не читает: для клонирования через этот прокси также задайтеGIT_SSL_CAINFO. -
Ваш стартовый скрипт выводит ошибку записи, содержащую
Resource temporarily unavailable, и завершается: ваш скрипт делит stdout или stderr с запускаемым им процессомclaude self-hosted-runnerили оркестратора, который переключил этот общий канал или сокет в неблокирующий режим. Когда канал заполнен, запись из вашего скрипта вместо ожидания завершается этой ошибкой операционной системы, и приset -eскрипт завершается. Чтобы устранить ошибку, направьте вывод runner’а или оркестратора в файл, который может создать пользователь вашего скрипта. Добавьте>> "$HOME/claude-runner.log" 2>&1к команде, которая его запускает, перед любым&, переводящим её в фоновый режим:Собственный вывод вашего скрипта по-прежнему идёт туда же, куда и раньше, а то, что runner пишет в stdout и stderr, включая ошибки запуска, попадает в этот файл. Используйте это перенаправление оболочки, а не только--log-file, который дублирует лог в файл, пока runner продолжает писать в stdout и stderr. Если вы уже передаёте--log-file, сохраните и его, поскольку перенаправление не меняет того, что попадает в этот файл. -
Оркестратор записывает в лог
poll failed:с ошибкой TLS-сертификата и продолжает повторные попытки: оркестратор не доверяет CA, выпустившему полученный сертификат, как бывает за прокси с инспекцией TLS. Задайте в окружении оркестратора переменнуюNODE_EXTRA_CA_CERTSс путём к PEM-файлу, содержащему корневой CA прокси. Задайте её также для runner’ов, которые запускает ваш хукspawn-runner. Например, строка может заканчиваться наpoll failed: self signed certificate in certificate chain — retrying in 1s, где между двоеточием и тире стоят слова TLS-библиотеки. Чтобы убедиться, что проблема устранена, проверьте, чтоconnectedимеет значениеtrueв теле ответа/healthzоркестратора. -
Сессии остаются в очереди: возможно, каждый работающий runner закреплён за другим владельцем. Проверьте метрику
claude_code_self_hosted_runner_locked_accountкаждого runner’а или полеlocked_accountего строки лога[runner:health], чтобы узнать, за кем он закреплён. Оба показывают email владельца только после того, как runner получил токен сессии с утверждениемact.email, чего никогда не бывает с сессиями агента Claude Tag. Без этого утверждения runner не выдаёт сериюlocked_accountи записывает в логlocked_account=yes, что говорит о том, что runner закреплён, но не за каким владельцем. Добавьте реплики или дождитесь, пока существующий runner завершит работу и перезапустится. Если окружение использует runner’ы по запросу, проверьте вместо этого оркестратор; см. Runner’ы по запросу. -
Сессии завершаются сбоем сразу после принятия: откройте сессию в claude.ai/code, чтобы увидеть ошибку. Если ошибка показывает, что git не смог пройти аутентификацию, проверьте учётные данные git runner’а. Для runner’а, запущенного с
--use-anthropic-git-proxy, см. Если сессии не запускаются на runner’е с git-прокси. Базовый каталог, недоступный для записи, останавливает runner при запуске, а не приводит к сбою сессий. См. пункт Runner завершается при запуске сcannot create or write to base directoryв этом списке. -
Сессия принята, но Claude так и не отвечает на её первое сообщение: если ваши runner’ы подключаются к
api.anthropic.comчерез прокси, настройте его так, чтобы ответ/worker/events/streamсессии передавался по мере поступления. Увеличение таймаутов runner’а не поможет. Чтобы подтвердить причину по логу runner’а, см. Сессия принята, но Claude так и не отвечает на её первое сообщение. -
Сессии не запускаются на runner’е, для которого задан
--use-anthropic-git-proxy: ищите в логе runner’аaccess denied by the git proxyили ошибку git, в которой указан адресapi.anthropic.com, содержащий/git_proxy/. Чтобы определить, обслуживала ли Anthropic сессию, и устранить причину, см. Если сессии не запускаются на runner’е с git-прокси. -
Сессии не могут выйти в сеть через исходящий прокси с аутентификацией: когда источник, заданный с помощью
--proxy-authorization-commandили--proxy-authorization-file, завершается сбоем, превышает таймаут в 30 секунд или возвращает пустое значение, runner отвечает на это соединение502 Bad Gatewayи записывает причину в лог. Runner скрывает stderr команды в этом логе и никогда не записывает в лог значение заголовка. При использовании--proxy-authorization-commandвыполните команду на хосте самостоятельно, чтобы убедиться, что она выводит полное значение заголовка в stdout. Если вместо этого runner завершается при запуске сcould not start the proxy-authorization listener, он не смог открыть свой loopback-слушатель. -
Runner записывает в лог строки
Poll failed, содержащиеrejecting the malformed poll response: runner получил ответ на опрос очереди работ, тело которого не является ожидаемым JSON очереди. Одна из причин — перехватывающий прокси или captive portal, отвечающий собственной страницей. Если runner подключается через прокси, настройте прокси так, чтобы ответы отapi.anthropic.comпередавались без изменений. Runner отклоняет некорректный ответ, учитывает его в категорииtransportметрикиclaude_code_self_hosted_runner_poll_errors_totalи повторяет попытку по расписанию для неудачных опросов, описанному в разделе Жизненный цикл сессии. Runner продолжает обслуживать свои активные сессии. До v2.1.246 runner воспринимал такой ответ как пустую очередь работ, что могло завершить его активные сессии или привести к его завершению. -
Сессия завершается сбоем с
no longer exists on the remoteилиcouldn't find remote ref: runner воспринимает сообщение gitcouldn't find remote refкак удалённую ветку. При этом ветка может по-прежнему существовать. Поищите её на вашем git-хосте точно под тем именем, что указано в сообщении, затем проверьте каждую подходящую причину:- Ветка удалена: восстановите её, как просит сообщение.
- Ветка существует, и вы не администрируете runner: отправьте администратору runner’а полное сообщение и сообщите, что ветка существует.
- Ветка существует, и у runner’а есть хук
checkout: команда git в вашем хуке может вывестиcouldn't find remote refв stderr до того, как хук завершится с ненулевым кодом. Найдите эту команду и исправьте ref, который она запрашивает, или remote, из которого она выполняет fetch. Runner передаёт вашему хуку ревизию, запрошенную сессией, в переменнойCLAUDE_RUNNER_REPO_REF. - Ветка существует, и у runner’а нет хука
checkout: runner сам получает ветку. Если к репозиторию применяется--git-host-rewrite, убедитесь, что на хосте, на который выполняется перезапись, есть ветка с тем же именем, и отправьте ветку туда, если её нет. - Ветка существует, и runner старше v2.1.301: обновите runner, как описано в разделе Закрепите версию, чтобы другой сбой показывал собственную ошибку. До v2.1.301 другие сбои, в выводе ошибки которых был
couldn't find remote ref, также отображались как удалённая ветка.
-
Сессия запускается без одного из своих репозиториев: на runner’е без хука
checkoutgit-хост может отклонить проверку доступа runner’а к репозиторию, из которого сессия только читает. Тогда runner пропускает этот репозиторий, записывает в лог строку[runner:warn] could not access context sourceс указанием отказа и запускает сессию с остальными репозиториями. Runner пропускает только явный отказ: хост отвечает, что репозиторий не найден, git не находит учётных данных для хоста или аутентификация не проходит. Сбой сети, таймаут или HTTP403по-прежнему приводят к сбою запуска сессии, как и отказ для репозитория, в который сессия отправляет результаты. Runner по-прежнему намеренно завершает сбоем сессию, которую пропуск оставил бы вообще без репозитория. С--use-anthropic-git-proxyrunner пропускает только репозиторий, доступ к которому отклоняет сам git-прокси. Проверка доступа выполняется заново при каждом запуске сессии на runner’е, поэтому, как только git-идентичность runner’а получит доступ на чтение, следующий запуск клонирует репозиторий. До v2.1.274 каждый из этих отказов приводил к сбою запуска сессии. -
Сессия не запускается с
This runner couldn't read: runner запросил у git-хоста репозиторий со своими учётными данными git и получил ответ «не найдено». Репозиторий не существует, или эти учётные данные не дают доступа к репозиторию.- Если вы администрируете runner: предоставьте его учётным данным git доступ на чтение к репозиторию, затем запустите новую сессию.
- Если вы запустили сессию: если репозиторий не существует, запустите новую сессию с правильным репозиторием. Если у runner’а нет доступа к репозиторию, попросите администратора runner’а предоставить доступ, затем запустите новую сессию.
-
Сессии запускаются или возобновляются несколько минут: одна из причин — клонирование с нуля. В парке, где каждый runner запускается на пустом диске без хука
checkout, runner заново клонирует репозитории сессии, прежде чем запустить Claude Code. Сессия, которая возобновляется на пустом диске, снова ждёт того же клонирования. Чтобы избежать клонирования с нуля, предоставьте предварительно подготовленный checkout. Если вы уже его предоставляете, убедитесь, что runner его использует. Если runner сам создаёт клон и завершается после одной сессии, уменьшитеCLAUDE_RUNNER_FETCH_DEPTHсо значения по умолчанию в 50 коммитов, чтобы получать меньше истории. Если вы используете хук, он выполняется вместо клонирования runner’ом, поэтому вместо этого проверьте, что делает ваш хук. Следите за метрикойclaude_code_self_hosted_runner_session_init_duration_seconds, чтобы видеть, сколько времени занимают запуски. -
Ходы завершаются с ошибкой 401: когда ход заканчивается ответом 401 или 403 от Anthropic API, runner получает новый
CLAUDE_CODE_OAUTH_TOKENот Anthropic и передаёт его сессии. Неудавшийся ход не повторяется. Этот токен краткосрочный, и runner ротирует его через stdin сессии. Когда получение токена не удаётся, runner записывает в лог строкуinference_token refresh failedс указанием, когда будет повторная попытка, и продолжает повторять попытки, пока работает сессия. Если все вызовы начинают завершаться сбоем примерно через 30 минут после начала сессии, вероятно, скрипт-обёртка отключил stdin сессии, поэтому ротации токена не могут до неё дойти; см. Сохраняйте подключёнными stdin и файловый дескриптор 3. До v2.1.274 runner прекращал повторные попытки получения токена после нескольких неудач и ждал следующего запланированного получения. Неудавшийся ход не вызывал получение токена, поэтому каждый ход завершался ошибкой 401 до следующего запланированного получения. -
Ход завершается в claude.ai/code с
This model needs a newer version of Claude Code: версия Claude Code, с которой ваши runner’ы запускают сессии, старше, чем требует модель сессии, поэтому API отклоняет запрос. Обновите runner’ы до версии, указанной в сообщении, или более поздней, как описано в разделе Закрепите версию. До этого пользователи могут выбрать в сессии другую модель. -
Под завершается принудительно во время drain: увеличьте
terminationGracePeriodSecondsкак минимум до значения, которое runner записывает в лог при запуске. См. Время завершения работы.
[runner:fatal], в stdout, а отладочный вывод — в stderr, всё в виде строк простого текста, а не JSON. Сбои базового каталога, описанные в пунктах выше, выводятся в stderr до этого момента. Сохраняйте оба потока с помощью --log-file, что также позволяет self-hosted-runner doctor отслеживать их, или с помощью средств сбора логов вашей платформы.
Дочерний процесс каждой сессии пишет отдельный отладочный лог. При сбое runner показывает конец этого лога рядом с сессией в claude.ai/code. Если вы не запустили runner с --remove-session-state, он также сохраняет лог неудавшейся сессии на диске и выводит его путь в лог runner’а.
Когда runner завершается
Не перезапускайте runner по запросу, поскольку его рабочее задание одноразовое. Runner, который завершается сразу после запуска, требует иного подхода, чем runner, завершившийся по любой другой причине.- Нормальное завершение: runner завершил свои сессии и выполнил drain, достиг времени вывода из эксплуатации или получил команду остановиться. Перезапустите его, чтобы у окружения снова была ёмкость. Эти завершения описаны в разделе Жизненный цикл runner’а.
- Неудачный запуск: runner не может запуститься с заданной ему конфигурацией или на заданном хосте, поэтому он завершается через несколько секунд после запуска и завершается так же при каждом перезапуске. Более быстрый перезапуск не поможет. Кому-то нужно прочитать его вывод и устранить причину.
- Потеря связи: runner, который не может связаться с Anthropic дольше срока своей аренды, например пока его хост находится в спящем режиме, может быть удалён из окружения. Когда удалённый runner снова подключается, он завершается. В его логе может быть строка
[runner:fatal], содержащаяrunner record gone server-sideили, после более длительного перерыва,poll auth failed. Runner не регистрируется повторно самостоятельно, поэтому перезапустите его.
Как распознать неудачный запуск
Когда runner не может запуститься, он выводит строку с причиной, а затем завершается. В большинстве случаев строка содержит[runner:fatal]. В некоторых случаях строка вместо этого начинается с error:, в том числе когда runner не может разобрать свои флаги, не может прочитать секрет окружения или не может создать базовый каталог или записать в него. Следующая строка тогда указывает на --help.
Большинство строк лога начинаются с временной метки и [self-hosted-runner], которые в примере ниже опущены. Например, runner, запущенный с git-прокси Anthropic и ёмкостью больше единицы, выводит строку вроде этой:
--log-file. Runner выводит строку error: до того, как откроет файл лога, поэтому ищите её в терминале или в логах контейнера, как отмечено в разделе Устранение неполадок.
При разборе неудачного запуска также помогут следующие моменты:
- Строки нет вовсе: runner, который принудительно завершил хост, не выводит ни той, ни другой строки. Если вывод заканчивается без строки
[runner:fatal]и без строкиerror:, проверьте, не остановил ли процесс хост или ваш оркестратор, например из-за превышения лимита памяти. - Код выхода: runner не выделяет отдельный код выхода для ошибок, повторяющихся при каждом запуске. Он завершается с одним и тем же кодом как при ошибке конфигурации, например неподдерживаемом сочетании флагов, так и при сбое, который может устраниться сам, например когда API остаётся недоступным на протяжении собственных повторных попыток runner’а. Решение об увеличении паузы принимайте исходя из того, как скоро runner завершился, а причину выясняйте по выводу runner’а.
- Окружение выглядит работоспособным: некоторые шаги запуска выполняются после регистрации runner’а в окружении, например
--configure-gitи настройка учётных данных git-прокси Anthropic. Если один из этих шагов завершается сбоем, окружение может ещё несколько минут после завершения процесса продолжать отображать этот runner, и страница Cloud environments может показывать Healthy, хотя ни один runner не принимает работу. Если сессии остаются в очереди в окружении, которое выглядит работоспособным, проверьте, не перезапускает ли ваш супервизор runner.
Перезапуск с растущей паузой
Способ получить растущую паузу зависит от вашего супервизора.-
Kubernetes: Deployment на этой странице не требует изменений. После завершения контейнера kubelet по умолчанию ждёт перед его перезапуском, и пауза растёт с каждым перезапуском до верхнего предела. Пауза сбрасывается, как только контейнер некоторое время проработает без завершения.
Kubelet применяет ту же паузу и после нормального завершения, если контейнер проработал недолго. Поэтому runner, который часто выполняет drain, тоже может показывать статус
CrashLoopBackOff, так что прочитайте вывод, прежде чем заключать, что runner не может запуститься. Команда ниже читает вывод последнего запуска одного пода Deployment:Если последний запуск был неудачным, строка[runner:fatal]илиerror:находится среди последних строк вывода. Чтобы прочитать последний запуск другого пода, укажите этот под вместоdeploy/claude-runner. -
Docker и Docker Compose: рецепт Compose на этой странице не требует изменений. С
restart: alwaysDocker ждёт всё дольше перед каждым перезапуском контейнера, который продолжает завершаться, до верхнего предела. Замените<container>на имя контейнера в команде ниже, которая показывает, сколько раз Docker перезапускал контейнер:Команда выводит число. Если число продолжает расти, Docker продолжает перезапускать runner. -
Юнит systemd: по умолчанию systemd ждёт одинаковое время
RestartSecперед каждым перезапуском и не увеличивает его, поэтому юнит сRestart=alwaysперезапускает runner, который не может запуститься, каждый раз с одним и тем же интервалом. Когда запуски происходят достаточно часто, чтобы достичь ограничения частоты запусков юнита (по умолчанию пять запусков за 10 секунд), systemd прекращает перезапускать юнит. Юнит остаётся остановленным, пока кто-нибудь не запустит его снова, что systemd разрешает после истечения интервала ограничения частоты запусков или послеsystemctl reset-failed. ПосколькуRestartSecприменяется к каждому перезапуску, большее значение также задерживает перезапуск после нормального завершения. Выберите значение, которое уравновешивает эти два случая, и настройте оповещения по счётчику перезапусков юнита. - Цикл в оболочке или собственный супервизор: примените то же правило самостоятельно. Начните с паузы в пять секунд. После каждого запуска, который завершился в пределах минуты, удваивайте паузу перед следующим перезапуском, максимум до пяти минут. После запуска, который длился минуту или дольше, вернитесь к пяти секундам.
Выясните, почему runner продолжает завершаться
Если runner несколько раз подряд завершился сразу после запуска, остановитесь и проверьте следующее, прежде чем снова его перезапускать.- Последняя строка
[runner:fatal]илиerror:: в ней сказано, почему runner остановился. Частые причины перечислены в разделе Устранение неполадок. - Сочетание флагов: git-прокси Anthropic требует
--capacity 1. Рецепты на этой странице используют бо́льшую ёмкость, поэтому уменьшите её, когда добавляете прокси в один из них. - Что доступно окружению службы: если runner запускается вручную, но завершается сбоем под вашим супервизором, сравните пользователя, домашний каталог,
PATHи лимит памяти.--configure-gitи git-прокси Anthropic требуют наличия git вPATHи доступного для записи~/.gitconfig. - Секрет окружения: если вы отозвали секрет или ошиблись при его вводе, runner выводит строку, содержащую
RegisterRunner auth failed. - Вкладка Activity окружения: откройте окружение и выберите Activity. Если там продолжают появляться новые runner’ы и ни один из них не принимает работу, ваш супервизор перезапускает runner.
Сессия принята, но Claude так и не отвечает на её первое сообщение
Если runner принимает сессию, но Claude так и не отвечает на первое сообщение, а ваши runner’ы подключаются кapi.anthropic.com через прокси, причиной может быть прокси. Anthropic отправляет сессии её сообщения через один потоковый ответ, который остаётся открытым. Если ваш прокси буферизует его или закрывает, прежде чем что-либо передать, первое сообщение так и не доходит. Другие запросы сессии при этом выполняются успешно.
Чтобы это исправить, измените то, как прокси обрабатывает этот запрос:
- Не буферизуйте ответ: передавайте его по мере поступления. Его тип содержимого —
text/event-stream. - Позвольте ответу оставаться открытым: он может оставаться открытым до часа. Если у прокси есть таймаут бездействия, сделайте его значительно больше 15 секунд, поскольку по умолчанию Anthropic отправляет keep-alive как минимум с такой частотой.
Подтвердите причину по логу runner’а
Эти шаги предназначены для runner’ов на Claude Code v2.1.287 или новее с уровнем логирования по умолчанию--log-level info. Более старые runner’ы записывают эти строки позже или не записывают вовсе.
- Дождитесь истечения
--startup-timeout-min, по умолчанию 15 минут. Затем runner останавливает сессию и записывает в лог строку, которая содержитno child output forи заканчивается на— releasing. - Найдите в логе runner’а
SDKStartup: SSE. Эти строки следуют за строкойfinished: interrupted, через несколько строк после строки из шага 1. У большинства из них нет временной метки, поэтому сборщик логов может показывать их в другом месте. - Найдите строку в этой таблице:
Если в логе нет ни одной из этих строк или в логе прокси нет признаков проблемы, сообщите о проблеме.
Остановите повторные попытки сессии
Сессия сохраняет сообщение как ожидающее, и runner, который может обслуживать владельца сессии, снова принимает сессию. Пока вы не измените прокси, сессия каждый раз ждёт так же. Чтобы это прекратить, архивируйте сессию в claude.ai/code.Что дальше
- Customize sessions: скрипты-обертки, lifecycle hooks, on-demand runners, MCP servers и разрешения
- Test end to end: проверьте новый образ runner из CI перед продвижением
- Reference: каждый флаг CLI, переменная окружения и метрика