SpyBara
Go Premium

self-hosted-environments-deploy.md 2026-10-21 23:02 UTC to 2026-10-22 22:58 UTC

This page contains 141 additions and 111 deletions.

2026
Fri 2 22:59 Sun 4 23:58 Mon 5 23:58 Tue 6 23:59 Sat 10 23:01 Wed 14 22:58 Mon 19 23:58 Tue 20 23:00 Wed 21 23:02 Thu 22 22:58 Fri 23 22:59 Sat 24 22:59 Sun 25 23:58 Mon 26 22:57 Tue 27 22:59 Wed 28 23:57 Thu 29 23:57

Развертывание самостоятельно размещаемых окружений в production

Запуск самостоятельно размещаемых runners в production: усиление безопасности, контроль сетевого исходящего трафика, учетные данные git, рецепты Kubernetes и Compose, а также устранение неполадок.

[!NOTE]

Самостоятельно размещаемые окружения находятся в публичной бета-версии на планах Team и Enterprise; раздел Доступность и ограничения охватывает путь включения. Эта страница охватывает запуск флота в production; см. быстрый старт для вашего первого runner и сеанса.

Самостоятельно размещаемое окружение запускает Claude Code облачные сеансы на runners, которые вы развертываете внутри вашей сети, и в production эти сеансы выполняют направленный моделью код от имени всех, кто может отправить сеанс в окружение. Эта страница предназначена для оператора, переводящего рабочее окружение в production. Она проходит через развертывание по порядку: что заблокировать перед подключением реальных систем, исходящий трафик, который требуется флоту, как сеансы аутентифицируются на вашем хосте git, сами рецепты развертывания и что проверить, когда сеансы работают неправильно.

Усиление безопасности развёртывания

Самостоятельно размещаемый 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 в сетевом пространстве имён контейнера сессии

    Блокировка применяется также к вашему скрипту-обёртке и хукам жизненного цикла, так как они используют тот же контейнер. Аутентифицируйте любой обмен токенами с помощью session JWT в вашем собственном сервисе токенов через разрешённый исходящий трафик или используйте веб-идентификацию на основе файлов, такую как IAM Roles for Service Accounts (IRSA) на Amazon EKS.

  • Изоляция файловой системы для каждого 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, чтобы узнать, где должны находиться эти гранты.

[!NOTE]

Если в вашей организации включён список разрешённых IP-адресов, добавьте публичные адреса исходящего трафика ваших runners и контейнеров сессий в список разрешённых перед их запуском. Если вы используете on-demand runners, добавьте также адрес хоста оркестратора. Не полагайтесь на список разрешённых как на сетевой контроль для трафика runner или сессий. Вместо этого применяйте default-deny egress на границе вашей собственной сети.

Требования к сети

Runner и порожденные им дочерние сеансы устанавливают исходящие соединения с хостами ниже. Ограничьте исходящий трафик контейнера сеанса этими хостами и конкретными внутренними сервисами, которые сеансам нужно достичь; Default-deny egress охватывает как и почему.

Эти хосты всегда требуются:

Хост Порт Используется для
api.anthropic.com 443, HTTPS; WSS для git под управлением Anthropic Плоскость управления runner и потоковая передача сессии, вывод модели, флаги функций, аналитика продукта, получение ключей JWKS, подпись коммитов и git под управлением Anthropic при установке --use-anthropic-git-proxy
Ваш git-хост, такой как github.com или ваш хост GitHub Enterprise 443 или 22 Клонирование и push репозиториев на каждом git-хосте, который используют сессии runner. Для runner, использующего --use-anthropic-git-proxy, см. когда путь к github.com всё ещё нужен.

Runner, использующий --use-anthropic-git-proxy, направляет свой git-трафик к github.com через api.anthropic.com, поэтому ему не нужен путь к git-хосту github.com. Этот путь всё ещё нужен, если вы устанавливаете --push-outcome-on-release или выполняете push из хука post-session.

Требуются ли эти хосты, зависит от вашей конфигурации:

Хост Порт Когда требуется
downloads.claude.ai 443 Во время установки, когда вы устанавливаете или обновляете Claude Code на хосте с помощью встроенного установщика; сам скрипт install.sh обслуживается с claude.ai. Во время выполнения сеанса только когда сеансы устанавливают плагины из официального рынка Anthropic.
storage.googleapis.com 443 Во время выполнения сеанса для количества установок плагинов и метаданных, показанных в /plugin.
code.claude.com и claude.com 443 Поиск документации встроенным агентом claude-code-guide и предварительно одобренные запросы WebFetch во время сеансов. Блокировка этих хостов влияет только на поиск документации.
*.frame.claudeusercontent.com 443 Только когда инструмент Artifact доступен для сеансов в вашей организации; значения по умолчанию варьируются по плану, согласно таблице доступности там. Установите CLAUDE_CODE_DISABLE_ARTIFACT=1 на runner, чтобы держать инструмент отключенным независимо от параметра организации.
registry.npmjs.org 443 Когда сеанс устанавливает плагин, как для получения пакетов плагинов npm-source, так и для установки зависимостей Node.js плагина, или когда запускается MCP-сервер, запущенный npx
http-intake.logs.us5.datadoghq.com 443 Метрики операций Anthropic. Только когда установлено CLAUDE_CODE_BYOC_ENABLE_DATADOG=1; отключено по умолчанию в самостоятельно размещаемых окружениях.
browser-intake-us5-datadoghq.com 443 Загрузки отчетов об ошибках Anthropic, отправляемые только когда отчетность об ошибках включена для учетной записи сеанса. Подавляется DISABLE_ERROR_REPORTING=1 или DISABLE_TELEMETRY=1.
Эндпоинты вашего облачного провайдера для запросов к модели, поиска моделей и обновления учётных данных, такие как bedrock-runtime.us-east-1.amazonaws.com или aiplatform.googleapis.com 443 Только когда runner отправляет запросы к модели в Amazon Bedrock или Google Cloud's Agent Platform

Эти хосты не нужно добавлять в список разрешённых хостов для трафика 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 не содержат URL http:// или 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 самостоятельно, например чтобы делать коммиты от имени собственного бота

Для репозиториев на github.com также можно запустить раннер с --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 и раннер не использует git под управлением Anthropic, раннер оставляет вашу настройку как есть, не устанавливает эти хуки и выводит предупреждение [runner:git].

Для подписи коммитов требуется git 2.34 или новее; раннер проверяет это при запуске и завершается с ошибкой, если ваш git старше. Этот флаг не настраивает учётные данные для push — их по-прежнему нужно предоставить в образе.

На раннере версии v2.1.280 или новее коммиты, которые вы делаете из хука жизненного цикла 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, чтобы конфигурация применялась независимо от того, от имени какого пользователя запущен процесс раннера:

RUN git config --system user.name "Claude" && \
    git config --system user.email "noreply@anthropic.com"

Без идентификации git commit завершается ошибкой Please tell me who you are, и сессии не могут продвигаться. Вместо этого можно использовать идентификацию собственного бота; раннер не переопределяет эти значения.

Не встраивайте долгоживущие или широко ограниченные учётные данные для push в общий образ раннера: учётные данные в образе доступны каждой сессии, которую запускает этот образ, кто бы её ни начал. Вместо этого выпускайте краткосрочный токен с минимальной областью действия для каждой сессии в своём скрипте-обёртке, используя идентификацию создателя сессии, декодированную из JWT сессии. Сочетайте это с эфемерным контейнером на каждую сессию, для чего требуется --capacity 1, чтобы никакие учётные данные не переживали сессию, которая их выпустила; см. раздел об усилении защиты.

Если вам необходимо настроить учётные данные для push на уровне образа, например для ключа развёртывания только для чтения, ограничьте их настолько жёстко, насколько позволяет ваш git-хостинг:

  • Ключ развёртывания SSH, ограниченный одним репозиторием, с перезаписью url.<base>.insteadOf
  • credential.helper, возвращающий токен с минимальной областью действия
  • GIT_SSH_COMMAND, указывающий на ключ с узкой областью действия

Какой бы механизм вы ни настроили, он должен работать без запроса ввода, потому что встроенные операции clone и fetch раннера отключают запросы, которые иначе показали бы git, SSH и Git Credential Manager:

  • Раннер задаёт GIT_TERMINAL_PROMPT=0, поэтому git не запрашивает имя пользователя или пароль.
  • Раннер запускает SSH с BatchMode=yes, добавленным к вашему GIT_SSH_COMMAND, если вы его задали, поэтому SSH не запрашивает парольную фразу или подтверждение хоста.
  • Раннер задаёт GCM_INTERACTIVE=never, поэтому Git Credential Manager не открывает диалог входа.
  • Раннер очищает core.askPass, поэтому если вы используете вспомогательную программу askpass, задайте её через переменную окружения GIT_ASKPASS.

Если ваш git-хостинг отклоняет учётные данные или вы их не настроили, раннер делает несколько повторных попыток, а затем завершает подготовку репозитория с ошибкой, если это репозиторий, в который сессия отправляет результаты. Для репозитория, который сессия только читает, в разделе Устранение неполадок описано, когда раннер вместо этого его пропускает. Раннер не передаёт эти настройки в окружение сессии.

Храните любую программу, указанную в GIT_SSH_COMMAND или GIT_ASKPASS, там, куда сессии не могут записывать, — так же, как контрольный список усиления защиты требует для каталога хуков и скрипта-обёртки. То же относится к любому ключу или файлу в командной строке этой программы. Собственный git раннера запускает эту программу при выполнении clone или fetch.

Если каталоги извлечения принадлежат другому uid, чем процесс раннера, git отказывается с ними работать; добавьте safe.directory:

RUN git config --system --add safe.directory '*'

Используйте git-прокси Anthropic

С git-прокси Anthropic, который также называется git под управлением Anthropic, образу раннера не нужны ключи SSH, вспомогательная программа учётных данных, .netrc или другие учётные данные git для самой сессии. Вместо этого раннер просит Anthropic обслуживать git для его сессий. Для пользовательской сессии, которую обслуживает Anthropic, clone раннера и собственные 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-прокси.

[!WARNING]

Когда задан --use-anthropic-git-proxy, раннер удаляет и заменяет глобальную конфигурацию git пользователя, от имени которого он запущен, и не сохраняет резервную копию. Он делает это при запуске и перед каждой сессией. Данные входа или вспомогательная программа учётных данных, которые вы там хранили, теряются. Настройки, которые записывает --configure-git, сохраняются. Запускайте раннер от имени отдельного пользователя или в контейнере, но никогда от своего имени.

Храните несекретные настройки git, такие как идентификация и safe.directory, в системной конфигурации git.

Включение git-прокси Anthropic

Прежде чем запускать раннер с --use-anthropic-git-proxy, убедитесь, что хост раннера отвечает каждому из этих требований. Раннер отказывается запускаться, если требование к capacity или к git не выполнено:

  • Claude Code v2.1.267 или новее: более ранние версии принимают флаг, но не сообщают Anthropic о запросе на обслуживание git и не выводят строку Registering as opted in, поэтому Anthropic не обслуживает их сессии.
  • --capacity 1, значение по умолчанию: каждый процесс раннера обрабатывает одну сессию за раз, поэтому для параллелизма запускайте больше реплик.
  • Git 2.32 или новее: более старые версии git игнорируют конфигурацию git для отдельной сессии, которую раннер настраивает для git-прокси.

[!WARNING]

Рецепты для Kubernetes и Docker Compose на этой странице используют --capacity 4. Если вы добавите --use-anthropic-git-proxy или CLAUDE_RUNNER_USE_GIT_PROXY=1 в один из них, не изменив capacity на 1, раннер будет завершаться при запуске каждый раз, когда оркестратор его перезапускает. Задайте --capacity 1 и запускайте больше реплик для параллелизма. В разделе Когда раннер завершается показана строка, которую выводит раннер.

Чтобы включить git-прокси, добавьте --use-anthropic-git-proxy в команду раннера или задайте CLAUDE_RUNNER_USE_GIT_PROXY=1 в окружении раннера. Эта команда, выполненная в оболочке на хосте раннера, запускает раннер из быстрого старта с включённым git-прокси:

claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxy

При запуске раннер выводит Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy). Затем Anthropic решает для каждой сессии на этом раннере, обслуживать ли её git. Для каждой обслуживаемой сессии раннер записывает в лог строку [runner:session], содержащую governed git ACTIVE. Если вместо этого сессия не запускается, см. Когда сессии не запускаются на раннере с git-прокси.

Как Anthropic обслуживает git для сессии

Для сессии, которую обслуживает Anthropic, clone раннера и собственные 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. Clone сессии завершается ошибкой, и ошибка 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 в окружении раннера, например в спецификации пода или в файле Compose, удалите её оттуда. В оболочке сбросьте её:

unset CLAUDE_RUNNER_USE_GIT_PROXY

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 предназначен для раннеров, использующих git под управлением Anthropic. Он поддерживает одну команду, gh api, которая вызывает REST API GitHub. Требуется Claude Code v2.1.287 или новее в образе раннера.

Эта команда открывает pull request вместо gh pr create. Встроенный gh подставляет {owner} и {repo} для текущего репозитория:

gh api repos/{owner}/{repo}/pulls -f title='Fix' -f head='my-branch' -f base='main'
  • Учётные данные: встроенный gh отправляет свои REST-запросы через git под управлением Anthropic, который предоставляет учётные данные GitHub на стороне Anthropic, поэтому образу не нужен токен GitHub для этого
  • Какие сессии его получают: Anthropic решает для каждой сессии, обслуживает ли git под управлением Anthropic gh этой сессии. Если да, строка [runner:session] governed git ACTIVE, которую раннер записывает в лог для сессии, показывает gh_path_shim=true. Если нет, у сессии нет gh
  • jq: установите jq в образ, если хотите, чтобы работал --jq
  • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: если окружение сессии задаёт эту переменную, Claude Code не предоставляет встроенный gh, и у сессии нет gh

Если образ включает GitHub CLI, сессии используют его.

Доверие частному центру сертификации при использовании git под управлением Anthropic

Этот раздел применим, если вы задаёте GIT_SSL_CAINFO или GIT_SSL_NO_VERIFY в окружении раннера, сессии которого используют git под управлением Anthropic. Описанная обработка требует, чтобы раннер работал на 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: не помогает за прокси, переподписывающим сертификаты. Собственный clone раннера через git под управлением Anthropic проверяет сертификаты, даже когда переменная задана, поэтому этот clone завершается ошибкой, пока git не начнёт доверять вашему CA одним из двух других способов.

Для git-соединений, которые передают токен сессии в git под управлением Anthropic, раннер применяет эти две переменные следующим образом. Хук command запускается с окружением сессии, поэтому получает то же, что и git внутри сессии:

  • GIT_SSL_CAINFO: по чему git проверяет git под управлением Anthropic, зависит от того, где запущен git:
    • Собственные clone и fetch раннера: выполняются без переменной и проверяют git под управлением Anthropic по файлу сертификатов для отдельной сессии, который записывает раннер. Раннер собирает этот файл из системного пакета CA хоста раннера и сертификатов из вашего файла.
    • Git внутри сессии: получает конфигурацию http.sslCAInfo с указанием вашего файла вместо переменной, а также записи http.<url>.sslCAInfo, которые проверяют git под управлением Anthropic по файлу для отдельной сессии.
    • Хуки checkout и post-session: наследуют переменную без изменений.
  • GIT_SSL_NO_VERIFY: какие проверки сертификатов остаются отключёнными, зависит от того, где запущен git:
    • Собственные clone и fetch раннера: выполняются без переменной и проверяют предъявленный им сертификат.
    • Git внутри сессии: получает конфигурацию http.sslVerify=false вместо переменной, поэтому проверки остаются отключёнными для других хостов. Он также получает записи http.<url>.sslVerify=true, которые сохраняют проверки включёнными для git под управлением Anthropic.
    • Хуки checkout и post-session: когда у сессии есть репозиторий в git под управлением Anthropic, получают конфигурацию http.sslVerify=false вместо переменной. Они также получают записи http.<url>.sslVerify=true, которые сохраняют проверки включёнными для git под управлением Anthropic.

Для файла сертификатов отдельной сессии нужен системный пакет CA по пути /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 использует ваш файл как есть для git под управлением Anthropic. Исправьте то, что указано в строке.

Для каждой сессии, использующей git под управлением Anthropic, раннер также записывает в лог строку [runner:warn], которая начинается с governed git: GIT_SSL_CAINFO is set или governed git: GIT_SSL_NO_VERIFY is set. Строка сообщает, что раннер сделал с этой переменной для собственного git, для git внутри сессии и для ваших хуков жизненного цикла. В конце она указывает, нужно ли вам что-либо менять.

Перезапись URL git для частных сетей

URL репозиториев поступают из плоскости управления в формате HTTPS с именем хоста вашего git-хостинга; для GitHub Enterprise это имя хоста, которое вы настроили для интеграции GitHub Enterprise на claude.ai. Два повторяемых флага перезаписывают эти URL перед clone:

  • --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 является минимальной отправной точкой:

FROM debian:bookworm-slim
ARG CLAUDE_CODE_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates openssh-client jq \
 && rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_CODE_VERSION:?set with --build-arg CLAUDE_CODE_VERSION}/linux-x64/claude" \
      -o /usr/local/bin/claude && chmod +x /usr/local/bin/claude
RUN git config --system user.name "Claude" \
 && git config --system user.email "noreply@anthropic.com" \
 && git config --system --add safe.directory '*'
ENTRYPOINT ["claude"]

Замените 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 или позже. Постройте образ, затем отправьте его в ваш реестр и ссылайтесь на него в рецептах ниже:

docker build \
  --build-arg CLAUDE_CODE_VERSION="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/stable)" \
  -t <your-registry>/claude-runner:latest .

Подстановка команды ищет текущий номер выпуска 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, а не убивает процессы в нем, поэтому сеансы на лимите работают медленнее, но продолжают работать.

В спецификации контейнера Kubernetes установите эти начальные значения с помощью следующего блока resources:

resources:
  requests:
    cpu: "2"
    memory: 4Gi
  limits:
    cpu: "4"
    memory: 4Gi

Сборки и тесты обычно являются самой большой и наиболее переменной частью нагрузки сеанса, поэтому запустите репрезентативную сборку вашего репозитория, измерьте его пиковый CPU и память и поднимите любое начальное значение, которое не оставляет места для процесса Claude Code на вершине этого пика.

Runner использует --capacity для ограничения количества сеансов, которые он запускает одновременно. Он не делит CPU или память между ними, поэтому сеансы на runner делят CPU и память контейнера. Чтобы ограничить долю одного сеанса, применяйте лимиты из вашего скрипта-обертки. То, что дать одному контейнеру, поэтому зависит от того, сколько сеансов он обслуживает одновременно:

  • Один сеанс на runner: дайте каждому контейнеру значения одного сеанса. Используйте эту размер на --capacity 1, которую раздел усиления безопасности рекомендует, и для on-demand runners, где вы устанавливаете значения на рабочую нагрузку, которую ваш spawn-runner hook отправляет, такую как шаблон pod Kubernetes Job.
  • Несколько сеансов на runner: на --capacity выше одного, умножьте значения одного сеанса на емкость, потому что до такого количества сеансов может работать в контейнере одновременно. Рецепты Kubernetes и Docker Compose запускают --capacity 4 без лимитов CPU или памяти, поэтому добавьте лимиты, размер которых соответствует емкости, которую вы запускаете.

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.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: claude-runner
  namespace: claude-runners
spec:
  replicas: 3
  selector:
    matchLabels:
      app: claude-runner
  template:
    metadata:
      labels:
        app: claude-runner
        app.kubernetes.io/part-of: claude-code-self-hosted-runner
    spec:
      terminationGracePeriodSeconds: 90
      containers:
        - name: runner
          image: <your-registry>/claude-runner:latest
          args:
            - self-hosted-runner
            - --environment-secret-file
            - /etc/claude/environment-secret
            - --capacity
            - "4"
            - --base-dir
            - /workspace
          volumeMounts:
            - name: environment-secret
              mountPath: /etc/claude
              readOnly: true
          ports:
            - name: health
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 30
      volumes:
        - name: environment-secret
          secret:
            secretName: claude-runner-environment-secret

Развертывание выше живет в пространстве имен claude-runners. Сначала создайте пространство имен:

kubectl create namespace claude-runners

Создайте поддерживающий Secret из локального файла, содержащего значение, которое вы скопировали на шаге Copy environment key в пользовательском интерфейсе администратора, чтобы секрет никогда не появлялся в истории вашей оболочки. Запустите (umask 077 && cat > ./environment-secret), вставьте секрет, нажмите Enter, затем Ctrl-D. Затем создайте Secret и удалите файл:

kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret

Docker Compose

Сервис Compose ниже перезапускает runner всякий раз, когда он выходит, что охватывает как сбои, так и нормальный выход после осушения. Политика перезагрузки Docker перезапускает тот же контейнер с его записываемым слоем нетронутым, поэтому runner возвращается на переиспользованной файловой системе, а не на свежей, которую рекомендует позиция усиления безопасности; используйте этот рецепт для оценки, и для production либо пересоздайте контейнер за запуск, либо используйте оркестратор, который это делает.

Docker ждёт дольше перед каждым перезапуском контейнера, который продолжает выходить, вплоть до потолка, поэтому runner, который не может запуститься, не продолжает перезапускаться в плотном цикле под этим рецептом. Когда runner выходит описывает, что проверить, когда это происходит.

services:
  claude-runner:
    image: <your-registry>/claude-runner:latest
    command:
      - self-hosted-runner
      - --environment-secret-file
      - /run/secrets/environment-secret
      - --capacity
      - "4"
      - --base-dir
      - /workspace
    secrets:
      - environment-secret
    restart: always
    stop_grace_period: 90s

secrets:
  environment-secret:
    file: ./environment-secret

Время завершения работы

После 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 состоит из трёх шагов:

  1. Runner ждёт до --drain-wait-sec секунд, по умолчанию 0, пока завершатся ещё выполняемые ходы.
  2. Он завершает дерево процессов каждой сессии, включая любую команду, которую Claude всё ещё выполнял, но не процесс, продолжающий работать после завершения своей shell-команды.
  3. Он запускает lifecycle-хук post-session.

Runner продолжает опрашивать Anthropic на протяжении всего drain. Благодаря этому его сессии остаются закреплены за ним, и другой runner не подхватит ни одну из них, пока ваш хук post-session ещё сохраняет незафиксированную работу.

Время, записываемое в лог, является суммой следующих значений:

При настройках по умолчанию это составляет 0 + 5 + 60 + 15 = 80 секунд. Более высокое значение --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. В разделе Время завершения работы перечислено, что увеличивает эти числа.

[!WARNING]

Если таймаут остановки истекает во время ожидания, ваш хост убивает runner, пока работа ещё выполняется. Его сессии не получают хук post-session. Runner также не отменяет регистрацию, поэтому control plane может потребоваться несколько минут, а иногда и больше, чтобы переставить их в очередь.

Опубликуйте статусное сообщение перед перезапуском

Перед перезапуском, который прервёт сессии, попросите 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 секунд.

На любом этапе runner выходит с кодом 0, как только он не удерживает никаких сессий. Второй сигнал сокращает этапы: runner немедленно выполняет drain, как он это делает при первом сигнале без --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.

Когда начинается drain, и снова при принудительном завершении, 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, которые их обслуживают.

Что путь переиспользования делает и не гарантирует:

  • Любая форма клона работает: полный, неглубокий или однозвездный клон на пути используется как есть. Runner никогда не передает --depth при получении в существующий клон, поэтому полный pre-warm держит свою полную историю и неглубокий остается неглубоким. CLAUDE_RUNNER_FETCH_DEPTH (full, 0 или число; по умолчанию 50) контролирует только холодный клон, который runner делает, когда клон еще не существует.

  • Отслеживаемые изменения сбрасываются, неотслеживаемые файлы сохраняются: при --capacity 1 каждая сессия начинается с жесткого сброса, который стирает отслеживаемые изменения предыдущей сессии, но runner никогда не запускает git clean, поэтому неотслеживаемые файлы из более ранних сессий заблокированного владельца остаются в дереве.

  • Каталоги для каждого сеанса также сохраняются: рядом с checkout runner создает записи для каждого сеанса под <base-dir>/_sessions/ для каждого сеанса, который он запускает. Локальный каталог конфигурации Claude сеанса содержит локальную копию транскрипта беседы. Рядом с ним находятся загруженные файлы сеанса, когда сеанс их имеет. Каталог сеанса находится там же: он содержит любые worktrees для каждого сеанса и checkout hook checkouts во время работы сеанса, и он сохраняет все остальное, что Claude написал в нем.

    По умолчанию runner оставляет их на месте, когда сеанс заканчивается, поэтому на диске, который пережил процесс runner, они накапливаются. Каждый сеанс запускается как собственный пользователь runner, поэтому любой более поздний сеанс, который обслуживает этот диск, может их прочитать. Если вы сохраняете постоянный --base-dir, размер тома для этого роста. То же самое применяется к любой установке, которая перезапускает runner на той же файловой системе, включая Docker Compose рецепт.

  • С --remove-session-state, каталоги для каждого сеанса не сохраняются: запустите runner с --remove-session-state, чтобы он удалял каталоги для каждого сеанса по мере завершения сеанса. Удаление является наилучшей попыткой: каталоги остаются, когда runner убивается до того, как его очистка запустится. Канонический клон и файлы, которые сеанс написал в другом месте на хосте, такие как временный каталог, остаются независимо.

  • С git proxy сброс превращается в checkout: с --use-anthropic-git-proxy runner очищает .git/ клона перед каждой сессией. Он сохраняет хранилище объектов и состояние shallow, а индекс удаляет. Без индекса каждая сессия начинается с полного checkout рабочего дерева вместо почти мгновенного сброса. Runner по-прежнему выполняет fetch в существующий клон, а не клонирует репозиторий заново. Pre-warm подмодулей при использовании прокси не поддерживается.

  • С git proxy pre-warm в формате reftable теряет свои refs: очищая .git/ перед каждой сессией, runner сохраняет .git/refs/ и удаляет каталог .git/reftable/. Клон, подготовленный в формате refs reftable в git, при этом теряет свои refs, поэтому выполняйте pre-warm клона в формате refs files.

  • Длинные клоны не нуждаются в обходном пути: runner ограничивает каждую операцию git с помощью 120-секундного наблюдателя без прогресса и 30-минутного жесткого лимита, а не плоского тайм-аута, поэтому медленный холодный клон, который продолжает сообщать о прогрессе, завершается.

Закрепите версию

Дочерний процесс каждой сессии по умолчанию запускает собственный бинарный файл 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 и вызывает ваш hook spawn-runner для загрузки одного на сеанс. См. On-demand runners.

Известные проблемы и ограничения

Ниже приведены ограничения в этом выпуске с обходными путями, где они существуют.

Трафик коннектора покидает вашу сеть

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/*

Если трафик инструмента должен остаться внутри вашей сети, запустите эквивалентные инструменты как локальные MCP-серверы на образе runner вместо этого. См. MCP servers.

Некоторые сеансы не считаются неактивными

Сеанс, держащий фоновую задачу, которая никогда не заканчивается, не считается неактивным, поэтому --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 завершает его, и любая работа работающего хода потеряна. Ход, ожидающий одобрения, запрошенного изнутри работающего вызова инструмента, - это один способ, которым сеанс переживает окно.

Выпущенный сеанс возобновляется с свежего клона, поэтому работа, которую он не отправил, потеряна в любом случае; см. Resumed sessions lose unpushed work. До v2.1.260, 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 клонирует его с помощью 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.

claude self-hosted-runner doctor

Частые проблемы:

  • 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 к команде, которая его запускает, перед любым &, переводящим её в фоновый режим:

    claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret >> "$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 в этом списке.

  • Сессии не запускаются на 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 воспринимает сообщение git couldn'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'а или устранения причины повторите сессию или запустите новую. Если ни одна из этих проверок не выявила причину, сообщите о проблеме.

    Для источника, в который сессия отправляет результаты, удалённая ветка приводит к сбою сессии. Для git-источника, из которого сессия только читает, runner пропускает этот источник и продолжает работу с остальными. Runner завершает сессию сбоем, если пропуск оставил бы её вообще без репозитория. До v2.1.228 такая сессия запускалась в пустом каталоге.

  • Сессия запускается без одного из своих репозиториев: на runner'е без хука checkout git-хост может отклонить проверку доступа runner'а к репозиторию, из которого сессия только читает. Тогда runner пропускает этот репозиторий, записывает в лог строку [runner:warn] could not access context source с указанием отказа и запускает сессию с остальными репозиториями.

    Runner пропускает только явный отказ: хост отвечает, что репозиторий не найден, git не находит учётных данных для хоста или аутентификация не проходит. Сбой сети, таймаут или HTTP 403 по-прежнему приводят к сбою запуска сессии, как и отказ для репозитория, в который сессия отправляет результаты. Runner по-прежнему намеренно завершает сбоем сессию, которую пропуск оставил бы вообще без репозитория. С --use-anthropic-git-proxy runner пропускает только репозиторий, доступ к которому отклоняет сам git-прокси.

    Проверка доступа выполняется заново при каждом запуске сессии на runner'е, поэтому, как только git-идентичность runner'а получит доступ на чтение, следующий запуск клонирует репозиторий. До v2.1.274 каждый из этих отказов приводил к сбою запуска сессии.

  • Сессия не запускается с This runner couldn't read: runner запросил у git-хоста репозиторий со своими учётными данными git и получил ответ «не найдено». Репозиторий не существует, или эти учётные данные не дают доступа к репозиторию.

    • Если вы администрируете runner: предоставьте его учётным данным git доступ на чтение к репозиторию, затем запустите новую сессию.
    • Если вы запустили сессию: если репозиторий не существует, запустите новую сессию с правильным репозиторием. Если у runner'а нет доступа к репозиторию, попросите администратора runner'а предоставить доступ, затем запустите новую сессию.
  • Сессии запускаются несколько минут: обычно основное время занимает первоначальное клонирование. Чтобы убедиться в этом, следите за метрикой claude_code_self_hosted_runner_session_init_duration_seconds, и сократите клонирование с помощью предварительно подготовленного checkout или меньшего значения CLAUDE_RUNNER_FETCH_DEPTH.

  • Ходы завершаются с ошибкой 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 пишет свой лог жизненного цикла, включая строки [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 продолжает завершаться сразу после запуска, и оповещал кого-либо, когда это продолжается.

В продакшене пусть супервизор перезапускает runner на чистом диске, в новом контейнере или VM, как рекомендует раздел Усильте защиту развёртывания.

Как распознать неудачный запуск

Когда runner не может запуститься, он выводит строку с причиной, а затем завершается. В большинстве случаев строка содержит [runner:fatal]. В некоторых случаях строка вместо этого начинается с error:, в том числе когда runner не может разобрать свои флаги, не может прочитать секрет окружения или не может создать базовый каталог или записать в него. Следующая строка тогда указывает на --help.

Большинство строк лога начинаются с временной метки и [self-hosted-runner], которые в примере ниже опущены. Например, runner, запущенный с git-прокси Anthropic и ёмкостью больше единицы, выводит строку вроде этой:

[runner:fatal] --use-anthropic-git-proxy requires --capacity 1 (the proxy URL is per-session and linked worktrees share origin). Omit --use-anthropic-git-proxy or set --capacity 1.

Ищите эту строку в стандартном выводе и стандартном потоке ошибок runner'а, в логах контейнеров вашей платформы или в файле, заданном с помощью --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:

    kubectl logs --previous -n claude-runners deploy/claude-runner
    

    Если последний запуск был неудачным, строка [runner:fatal] или error: находится среди последних строк вывода. Чтобы прочитать последний запуск другого пода, укажите этот под вместо deploy/claude-runner.

  • Docker и Docker Compose: рецепт Compose на этой странице не требует изменений. С restart: always Docker ждёт всё дольше перед каждым перезапуском контейнера, который продолжает завершаться, до верхнего предела. Замените <container> на имя контейнера в команде ниже, которая показывает, сколько раз Docker перезапускал контейнер:

    docker inspect --format '{{.RestartCount}}' <container>
    

    Команда выводит число. Если число продолжает расти, 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.

Для пошаговой диагностики на хосте runner'а запустите подкоманду doctor.

Что дальше

  • Customize sessions: скрипты-обертки, lifecycle hooks, on-demand runners, MCP servers и разрешения
  • Test end to end: проверьте новый образ runner из CI перед продвижением
  • Reference: каждый флаг CLI, переменная окружения и метрика