Настройка сессий в самостоятельно размещаемых окружениях
Настройте сессии самостоятельно размещаемого окружения с помощью скриптов-оболочек для учётных данных для каждой сессии, хуков жизненного цикла и порождения средств выполнения по требованию.
[!NOTE]
Самостоятельно размещаемые окружения находятся в публичной бета-версии на планах Team и Enterprise; Owner включает их, активируя Allow self-hosted environments на странице администратора Cloud environments. На этой странице предполагается наличие работающего средства выполнения; см. быстрый старт для настройки и Deploy to production для рецептов флота.
Самостоятельно размещаемое окружение запускает облачные сессии Claude Code на вашей собственной инфраструктуре, выполняемые процессом средства выполнения, который вы развёртываете. Без конфигурации это средство выполнения клонирует репозиторий сессии, порождает Claude Code и выполняет очистку. Эта страница предназначена для инженера платформы, управляющего средствами выполнения: она охватывает точки расширения для случаев, когда эти значения по умолчанию не подходят, от подготовки учётных данных для каждой сессии до полной замены checkout. Оболочки и хуки запускаются как исполняемые файлы на хосте средства выполнения, который работает на Linux или macOS, и примеры на этой странице предполагают оболочку POSIX.
Несколько переменных окружения хука на этой странице по-прежнему используют pool, такие как CLAUDE_RUNNER_POOL_ID; флаги CLI и имена переменных окружения используют environment, такие как --environment-secret-file.
Скрипты-обёртки
Используйте скрипт-обёртку, когда каждой сессии требуется настройка, которую средство выполнения не может выполнить самостоятельно:
- Подготовка краткосрочных учётных данных, ограниченных создателем сессии
- Экспорт секретов, специфичных для окружения
- Подготовка цепочек инструментов языка
- Применение ограничений ресурсов вокруг дочернего процесса
Средство выполнения запускает вашу обёртку вместо двоичного файла Claude Code каждый раз, когда запускает сессию, в том числе когда сессия возобновляется или повторно ставится в очередь. Завершите обёртку, выполнив exec в $CLAUDE_RUNNER_CLAUDE_BIN, собственный двоичный файл средства выполнения, чтобы сигналы и коды выхода распространялись правильно.
Укажите в --exec-path или SELF_HOSTED_RUNNER_EXEC_PATH путь к обёртке при запуске средства выполнения, заменив <writable-dir>, как описано в разделе быстрого старта:
claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --base-dir '<writable-dir>' --exec-path /etc/claude/session-wrapper.sh
Средство выполнения устанавливает следующие переменные в окружении обёртки:
| Переменная | Описание |
|---|---|
CLAUDE_CODE_SESSION_ACCESS_TOKEN |
JWT сессии с префиксом sk-ant-cc-. Его утверждение act идентифицирует создателя сессии, вместе с электронной почтой создателя, если интерфейс, создавший сессию, её записал. Значение — это токен на момент порождения процесса; обновления поступают через stdin дочернего процесса, поэтому обёртка видит только начальное значение. См. Проверка идентичности сессии. |
CCR_SESSION_ACCOUNT_EMAIL |
Электронная почта создателя сессии, предварительно извлечённая средством выполнения из утверждения act.email токена без проверки подписи. Подходит для маркировки, например для трейлеров коммитов. Когда электронная почта управляет выдачей учётных данных, вместо этого проверьте токен и прочитайте утверждение из него. См. Подготовка учётных данных, ограниченных создателем сессии. Не установлена, когда токен не содержит электронную почту создателя, например в сессиях, которые создаёт служебная учётная запись вашей организации. Рассматривайте как персональные данные. |
CLAUDE_RUNNER_CLIENT_PLATFORM |
Клиентский интерфейс, который создал сессию, например web_claude_ai, desktop_app, ios, claude_code_cli или scheduled_trigger. Anthropic записывает значение один раз при создании сессии, поэтому обёртка и каждый хук жизненного цикла видят одно и то же значение. Используйте его только для аналитики внедрения и маркировки, а не как сигнал авторизации. Не установлена, когда у сессии нет записанного или распознанного интерфейса. Требует Claude Code v2.1.229 или новее. |
CLAUDE_RUNNER_CLAUDE_BIN |
Абсолютный путь к собственному двоичному файлу Claude Code средства выполнения. Завершите вашу обёртку командой exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@", чтобы передать управление закреплённому двоичному файлу без жёсткого кодирования пути установки. |
CLAUDE_CODE_REMOTE_SESSION_ID |
ID сессии в форме с тегом cse_.... Это та же сессия, которую хуки жизненного цикла видят как CLAUDE_RUNNER_SESSION_ID в форме session_...; переменные UUID совпадают в обоих случаях, а замена префикса cse_ на session_ даёт ID, показанный в URL сессии. |
CLAUDE_CODE_REMOTE_SESSION_UUID |
Тот же ID сессии в канонической форме UUID для систем, которые используют UUID в качестве ключа. |
CLAUDE_CODE_REMOTE_SLACK_THREAD_URL |
Для сессии Claude Tag, которая относится к одной ветке Slack, — ссылка на эту ветку. Не установлена для других сессий и может быть не установлена и для сессии ветки. |
CLAUDE_CODE_REMOTE_SLACK_THREAD_TS |
Для сессии Claude Tag, которая относится к одной ветке Slack, — временная метка Slack этой ветки, например 1700000000.000100. Может быть не установлена, а также может быть установлена, когда CLAUDE_CODE_REMOTE_SLACK_THREAD_URL не установлена, поэтому проверяйте каждую переменную отдельно. |
CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID |
Для сессии Claude Tag — ID канала Slack или личного сообщения, которое обслуживает сессия или в котором находится её ветка, например C0123ABCDE. Временная метка Slack идентифицирует сообщение в пределах одного канала, поэтому записывайте этот ID вместе с CLAUDE_CODE_REMOTE_SLACK_THREAD_TS. Не установлена для других сессий и может быть не установлена и для сессии Claude Tag. |
CLAUDE_SESSION_INGRESS_TOKEN_FILE |
Абсолютный путь к файлу для каждой сессии, содержащему текущий JWT сессии, который поддерживается в актуальном состоянии при обновлении токенов. Подпроцессы оболочки читают его для своего заголовка Authorization при загрузке вложений, которые пользователь добавил в сессию. exec сохраняет переменную автоматически; обёртка, которая перестраивает окружение дочернего процесса, должна перенести переменную, иначе загрузки вложений молча прекратятся. |
CLAUDE_CONFIG_DIR |
Каталог конфигурации Claude для каждой сессии, записываемый при запуске сессии из снимка конфигурации хоста средства выполнения, который средство выполнения захватывает при запуске; см. Разрешения и подтверждение инструментов. Записи здесь изолированы для этой сессии. Каталог остаётся в <base-dir>/_sessions/ после завершения сессии, если вы не запустите средство выполнения с --remove-session-state; см. Повторное использование предварительно подготовленного checkout. |
CLAUDE_CODE_REMOTE_MCP_CONFIG |
Путь к файлу в CLAUDE_CONFIG_DIR, содержащему определения MCP-серверов, которые плоскость управления Anthropic доставила для этой сессии. Если ваша обёртка перестраивает окружение дочернего процесса, перенесите эту переменную. Без неё, когда плоскость управления обновляет доставленные серверы, дочерний процесс завершает работу любого сервера stdio, HTTP или SSE, добавленного вашей обёрткой с помощью --mcp-config, которого нет в обновлении. Требует Claude Code v2.1.299 или новее. |
ANTHROPIC_BASE_URL |
Базовый URL API, который будет использовать дочерний процесс, доставляемый плоскостью управления для каждой сессии; обычно https://api.anthropic.com. Не переопределяйте его: учётные данные вывода сессии — это выданный Anthropic токен OAuth, который другие поставщики не принимают. |
CLAUDE_CODE_OAUTH_TOKEN |
Краткосрочный токен доступа OAuth, который дочерний процесс использует для вывода модели, со временем жизни около 30 минут. Средство выполнения перевыпускает его до истечения срока и доставляет ротацию через stdin дочернего процесса, поэтому обёртка, которая не сохраняет stdin подключённым, видит только начальное значение. Не полагайтесь на список разрешённых IP-адресов вашей организации для ограничения использования этого токена: рассматривайте его как Bearer-токен, который остаётся пригодным для использования примерно 30 минут в случае утечки, и не записывайте его в лог, не сохраняйте на диск и не пересылайте за пределы контейнера сессии. |
CLAUDE_RUNNER_ALIVE_EVERY_MS |
Средство выполнения задаёт, как часто (в миллисекундах) дочерний процесс сообщает, что он активен, когда ход начинается до инициализации дочернего процесса. Если ваша обёртка перестраивает окружение дочернего процесса, перенесите эту переменную, иначе средство выполнения может освободить сессию во время этого хода и записать в лог no child output for <n> — releasing. Требует v2.1.309 или новее на средстве выполнения и в дочернем процессе. |
Обёртка также наследует остальную часть управляемого окружения дочернего процесса, включая любые переменные окружения, предоставленные сервером. exec передаёт всё это автоматически; если ваша обёртка порождает дочерний процесс другим способом, пересылайте окружение полностью.
Переменные CLAUDE_CODE_REMOTE_SLACK_* можно читать в вашей обёртке или хуке command, а также в том, что запускает сессия, например в shell-командах, хуках git и хуках Claude Code. Хуки checkout, post-session и spawn-runner их не получают.
Задавайте значение по умолчанию для переменных, которые могут быть не установлены
Если ваш скрипт использует set -u, раскрывайте CCR_SESSION_ACCOUNT_EMAIL, CLAUDE_RUNNER_CLIENT_PLATFORM, CLAUDE_CODE_REMOTE_SLACK_THREAD_URL, CLAUDE_CODE_REMOTE_SLACK_THREAD_TS и CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID со значением по умолчанию, например "${CCR_SESSION_ACCOUNT_EMAIL:-}". Каждая из них может быть не установлена, а при set -u Bash останавливается с ошибкой unbound variable, когда раскрывает неустановленную переменную.
Заключайте в кавычки аргументы и переменные, которые раскрывает ваш скрипт
Заключайте в кавычки каждый аргумент и каждую переменную, которые средство выполнения передаёт вашей обёртке или хуку command. Некоторые значения содержат символы, на которые реагирует оболочка, поскольку в них находится JSON, целые предложения или правило разрешений, например Bash(npm run test *).
- Аргументы: передавайте их дальше как
"$@" - Переменные: заключайте каждую в кавычки, как в
"${CLAUDE_CODE_REMOTE_SLACK_THREAD_URL:-}". Ссылка на ветку Slack, например, может содержать?и&. - Строки
evalиsh -c: не подставляйте значение в строку, которую выполняетevalилиsh -c, даже внутри кавычек. Вместо этого пусть такая строка ссылается на саму переменную.
Сохраняйте stdin и дескриптор файла 3 подключёнными
stdin дочернего процесса — это канал управления средства выполнения. Ротации токенов и сигналы завершения сессии поступают через него. Средство выполнения также открывает канал на дескрипторе файла 3 и читает из него сигналы активности дочернего процесса для управления таймаутами простоя и запуска. Простой exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" сохраняет оба автоматически.
Если ваша обёртка переводит дочерний процесс в фоновый режим с помощью простого &, она разрывает stdin дочернего процесса. Сессия выглядит работоспособной до истечения примерно 30-минутного времени жизни начального токена OAuth, после чего каждый вызов API, использующий этот токен, завершается ошибкой 401 authentication_error. Если ваша обёртка должна переводить дочерний процесс в фоновый режим, например чтобы сохранить ловушку очистки, сохраните stdin на дескрипторе файла 9 и явно подключите его заново:
exec 9<&0 # the runner uses 3, and from v2.1.309 reserves 4 and 5
"$CLAUDE_RUNNER_CLAUDE_BIN" "$@" <&9 9<&- &
CHILD=$!
trap 'teardown' EXIT
wait "$CHILD"
Перенаправлять stdout дочернего процесса можно. Дескриптор файла 3 и stderr должны оставаться подключёнными к средству выполнения:
- Дескриптор файла 3: передаёт сигналы активности дочернего процесса средству выполнения. Не закрывайте и не переиспользуйте его в обёртке.
- Дескрипторы файлов 4 и 5: средства выполнения на Claude Code v2.1.309 или новее резервируют их для дочернего процесса. Не закрывайте и не переиспользуйте их в обёртке. Если ваша обёртка использует какой-либо из них, например для сохранения stdin, перенесите это использование на дескриптор с 6 по 9 — более ранние версии также оставляют их свободными.
- stderr: когда обёртка или дочерний процесс завершается с ненулевым кодом выхода, средство выполнения публикует последние строки stderr в сессию и выводит их в свой собственный лог. Пользователь сессии видит эти строки, поэтому не выводите секреты в stderr и удалите
set -xперед развёртыванием обёртки. Если вы перенаправите stderr, сессии по-прежнему будут работать, но средство выполнения будет сообщать о сбое только с кодом выхода.
Передавайте флаги системного промпта дальше
Системный промпт и дополнительный системный промпт, которые плоскость управления Anthropic отправляет для сессии, поступают в вашу обёртку в виде путей к файлам, а не встроенного текста. Средство выполнения записывает каждый промпт в файл в каталоге конфигурации сессии CLAUDE_CONFIG_DIR и передаёт его путь в аргументах, которые получает ваша обёртка, как --system-prompt-file <path> или --append-system-prompt-file <path>.
Средства выполнения на Claude Code v2.1.281 или новее передают промпты в виде файлов. До v2.1.281 средство выполнения передавало их как --system-prompt <text> и --append-system-prompt <text>.
В вашем скрипте-обёртке или хуке command обрабатывайте эти флаги следующим образом:
- Передавайте их дальше: завершайте обёртку командой
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@", которая передаёт файловые флаги вместе со всеми остальными аргументами. Не удаляйте и не переписывайте их. Если сессия теряет файловый флаг промпта, она работает без инструкций, которые для неё отправила плоскость управления. - На средстве выполнения версии v2.1.281 или новее добавленный вами файловый флаг заменяет серверный, а не дополняет его: каждый файловый флаг промпта принимает одно значение, и Claude Code использует последнее вхождение, поэтому если вы добавите
--append-system-prompt-file <path>после"$@", содержимое вашего файла заменит дополнительные инструкции сервера. Чтобы добавить инструкции поверх серверных, поместите их вCLAUDE.mdобраза средства выполнения, который средство выполнения добавляет в пользовательскую конфигурацию каждой сессии.
Исправление Argument list too long при запуске сессии
В Linux, если вы объединяете промпт плоскости управления и собственный текст в один аргумент --append-system-prompt, средство выполнения записывает в лог строку такого вида, как только аргумент достигает 128 КБ:
Session <id> finished: failed — reason: child exited 126: /opt/wrapper.sh: line 9: /usr/local/bin/claude: Argument list too long
Linux ограничивает каждый отдельный аргумент 32 страницами памяти, то есть 128 КБ при обычных страницах размером 4 КБ. Значение getconf ARG_MAX, часто равное 2 МБ, — это отдельное ограничение на все аргументы и окружение вместе.
На Claude Code v2.1.283 или новее оставьте файловые флаги в "$@" и передавайте как --append-system-prompt только собственный текст. Claude Code использует оба: сначала промпт плоскости управления, затем ваш собственный текст:
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --append-system-prompt "$(cat /opt/team-instructions.md)"
В Linux, если ваш собственный текст достигает 128 КБ, перенесите его в CLAUDE.md образа средства выполнения, поскольку это по-прежнему один аргумент.
На v2.1.281 или v2.1.282 эта команда завершается с ошибкой Cannot use both --append-system-prompt and --append-system-prompt-file. На более ранних версиях ваш текст без какой-либо ошибки заменяет промпт плоскости управления. В любом из этих случаев сначала обновитесь или перенесите собственный текст в CLAUDE.md.
Подготовка учётных данных, ограниченных создателем сессии
Используйте подкоманду decode-token для чтения утверждений из JWT сессии. Она читает токен из аргумента, из CLAUDE_CODE_SESSION_ACCESS_TOKEN или из stdin, в этом порядке; о том, что она проверяет, см. Проверка токена внутри сессии. Пример ниже декодирует идентичность создателя, обменивает её на краткосрочные учётные данные AWS и выполняет exec в Claude Code:
#!/bin/bash
# Key on the stable Anthropic user ID and require a human creator.
CREATOR_SUB=$("$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token \
| jq -re '.act.sub // "" | select(startswith("user:"))') \
|| { echo "decode-token: verification failed or no human creator" >&2; exit 1; }
creds=$(your-sts-helper assume-role --subject "$CREATOR_SUB") \
|| { echo "credential exchange failed" >&2; exit 1; }
eval "$creds"
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"
Используйте jq -re вместо jq -r, когда извлечённое утверждение определяет решение об авторизации, чтобы при отсутствии утверждения команда завершалась с ненулевым кодом выхода, а не передавала дальше буквальную строку null. Сессии, созданные служебной учётной записью организации, например сессии ботов и агентов, содержат субъект agent: вместо user:, поэтому этот пример отклоняет их; если ваше окружение обслуживает такие сессии, явно решите, должна ли обёртка для них переходить к учётным данным по умолчанию вместо завершения. Если вашему обмену учётными данными вместо этого нужна электронная почта, прочитайте .act.email и обработайте её отсутствие: токен содержит её только тогда, когда интерфейс, создавший сессию, её записал, а у сессии, запущенной через CLI, её может не быть. Полный справочник по утверждениям и описание проверки из служб вне средства выполнения см. в разделе Проверка идентичности сессии.
Lifecycle hooks
Lifecycle hooks заменяют этапы конвейера runner'а для каждой сессии вашими собственными скриптами. Укажите runner'у на директорию с hooks с помощью --hooks-dir <path> или SELF_HOSTED_RUNNER_HOOKS_DIR. Runner ищет исполняемые файлы с известными именами; любой hook, который отсутствует, переходит к встроенному поведению, поэтому вам нужно написать только те, которые вам нужны. Hooks запускаются с собственными привилегиями runner'а, и дочерние процессы сессии используют тот же UID, поэтому монтируйте директорию hooks в режиме только для чтения или встраивайте её в образ, чтобы код сессии не мог её изменять; см. раздел об усилении безопасности.
Эти hooks отличаются от Claude Code hooks, которые запускаются внутри сессии; lifecycle hooks запускаются на runner'е, вокруг сессии.
checkout
Запускается для каждого репозитория каждый раз, когда runner запускает сессию, в том числе когда сессия возобновляется или повторно ставится в очередь, вместо встроенного клонирования и получения данных runner'а. Используйте хук для клонирования из зеркала сквозного доступа, к которому вы обращаетесь по HTTPS или SSH, инициализации рабочего дерева из архива или применения аутентификации git для каждой сессии.
Runner устанавливает эти переменные, а также может устанавливать другие переменные CLAUDE_RUNNER_, которых нет в таблице:
| Variable | Description |
|---|---|
CLAUDE_RUNNER_REPO_URL |
URL репозитория для клонирования, после применения любых --git-host-rewrite и --git-ssh-rewrite |
CLAUDE_RUNNER_REPO_REF |
Ревизия для checkout в том виде, в каком её запросила сессия: ветка, тег, SHA коммита или полное имя ссылки, например refs/heads/<branch> или refs/pull/<number>/head. Пустое значение означает ветку по умолчанию репозитория. |
CLAUDE_RUNNER_CHECKOUT_PATH |
Абсолютный путь, где должно остаться рабочее дерево |
CLAUDE_RUNNER_SESSION_ID |
ID сессии в форме с тегом session_..., для логирования и корреляции |
CLAUDE_RUNNER_SESSION_UUID |
Тот же ID сессии в канонической форме UUID |
CLAUDE_RUNNER_API_BASE_URL |
Базовый URL API Anthropic для вызовов в области сессии |
CLAUDE_RUNNER_CLIENT_PLATFORM |
Интерфейс клиента, который создал сессию, например web_claude_ai, desktop_app или ios. Не установлена, если у сессии нет записанного или распознанного интерфейса, поэтому при set -u ссылайтесь на неё как "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}". Требуется Claude Code v2.1.229 или новее. |
CLAUDE_CODE_SESSION_ACCESS_TOKEN |
Токен доступа сессии для вызовов API в области сессии |
GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_n |
Настройки git, которые runner фиксирует для git, запускаемого вашим хуком. Они описаны в разделе Конфигурация git внутри хуков жизненного цикла. Требуется Claude Code v2.1.280 или новее. |
Скрипт должен оставить в CLAUDE_RUNNER_CHECKOUT_PATH рабочее дерево, переключённое на запрошенную ревизию. Отсоединённый HEAD подходит, потому что runner создаёт рабочую ветку сессии поверх него.
После возврата вашего хука runner проверяет, что CLAUDE_RUNNER_CHECKOUT_PATH содержит .git. Если ваш хук материализует источник, не основанный на git, такой как Perforce или распакованный tarball, установите CLAUDE_RUNNER_SKIP_GIT_VERIFY=1 в окружении runner'а, чтобы пропустить эту проверку. Потоки на основе Git, такие как создание рабочей ветки и отправка результатов, требуют checkout git, поэтому экспортируйте результаты из деревьев, не основанных на git, с помощью хука post-session.
Получение учётных данных git в хуке
Runner не передаёт учётные данные git в хук. Подкоманда decode-token здесь тоже недоступна, потому что CLAUDE_RUNNER_CLAUDE_BIN не установлена в окружении хука checkout. Вместо этого создайте учётные данные клонирования для каждой сессии на основе идентичности сессии или вернитесь к собственной аутентификации git хоста:
- Учётные данные клонирования для каждой сессии: проверьте
CLAUDE_CODE_SESSION_ACCESS_TOKENс помощью стандартной библиотеки JWT по эндпоинту JWKS подCLAUDE_RUNNER_API_BASE_URL, как описано в разделе Verify the token from your service. Затем попросите вашу службу учётных данных выдать краткосрочные учётные данные клонирования для идентичности из утвержденияactтокена. Привязывайте эти учётные данные кact.subи не требуйтеact.email. - Аутентификация git хоста: используйте любую аутентификацию git, которая уже есть на хосте, например SSH-агент, помощник учётных данных или
.netrc.
Когда хук завершается ошибкой
Хук считается завершившимся ошибкой, если он завершается с ненулевым кодом или завершается с кодом 0, не оставив пригодного для использования checkout:
-
Репозиторий, в который сессия отправляет результаты: runner отмечает сессию как неудачную, и при ненулевом выходе выводит пользователю конец stderr скрипта. Запишите причину в stderr одной короткой строкой, сделайте это последним действием, а затем завершите работу с ненулевым кодом. Не выводите секреты в stderr и удалите
set -xперед развёртыванием хука.Если сообщение git
couldn't find remote refвстречается примерно в последних 4 КБ stderr хука, runner вместо этого сообщает об удалении ветки. Тогда в сообщении об ошибке сессии говорится, что ветка больше не существует в удалённом репозитории, и пользователю предлагается восстановить её. Если эта ветка существует, см. Сессия завершается ошибкойno longer exists on the remoteилиcouldn't find remote ref. -
Репозиторий, из которого сессия только читает, например репозиторий, добавленный к запущенной сессии: runner пропускает его и продолжает работу с оставшимися репозиториями. Runner отправляет в сессию шаг
Skippedи удаляет всё, что хук оставил по пути checkout.Если шаг
Skippedупоминает хук checkout, подробности ошибки ищите в строке[runner:warn] checkout hook failedв логе runner'а. Если в шаге сказано, что исходная ссылкаno longer exists on remote, см. Сессия завершается ошибкойno longer exists on the remoteилиcouldn't find remote ref. Если после пропуска у сессии не остаётся ни одного репозитория, runner всё равно отмечает сессию как неудачную.
Если хук завершается успешно, runner удаляет путь checkout после завершения сессии.
post-session
Запускается после выхода дочернего процесса Claude Code и перед тем, как runner разбирает рабочее пространство. Этот хук — ваш единственный шанс сохранить незафиксированную работу, потому что незафиксированные отслеживаемые изменения не сохраняются ни в одном из случаев:
- При
--capacityвыше одного: runner удаляет worktree каждой сессии сразу после возврата хука - При
--capacity 1: переиспользуемый канонический клон жёстко сбрасывается при запуске следующей сессии
Типичные применения — отправка ветки снимка незафиксированных изменений, архивирование логов или отправка события завершения сессии в ваши собственные системы.
Hook срабатывает при каждом завершении сессии, где был порождён дочерний процесс, независимо от причины; значения CLAUDE_RUNNER_EXIT_REASON ниже перечисляют случаи. Он не может срабатывать, когда runner завершается неожиданно, такой как вытеснение VM или потеря питания; если вам нужны гарантии против неожиданного завершения, делайте снимки периодически изнутри сессии с помощью Claude Code PostToolUse hook вместо этого. Runner устанавливает:
| Variable | Description |
|---|---|
CLAUDE_RUNNER_SESSION_ID |
ID сессии в форме с тегом session_... |
CLAUDE_RUNNER_SESSION_UUID |
Тот же ID сессии в канонической форме UUID |
CLAUDE_RUNNER_EXIT_REASON |
Как завершилась сессия; см. значения ниже таблицы |
CLAUDE_RUNNER_WORKSPACE_PATHS |
Разделённые двоеточиями абсолютные пути рабочих деревьев сессии. Пусто для сессий с нулевым репозиторием. |
CLAUDE_RUNNER_DEBUG_LOG_PATH |
Путь к логу отладки сессии, всё ещё на диске во время выполнения hook'а |
CLAUDE_RUNNER_API_BASE_URL |
Базовый URL API Anthropic для вызовов в области сессии |
CLAUDE_RUNNER_CLIENT_PLATFORM |
Интерфейс клиента, который создал сессию, например web_claude_ai, desktop_app или ios. Не установлена, если у сессии нет записанного или распознанного интерфейса, поэтому при set -u ссылайтесь на неё как "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}". Требуется Claude Code v2.1.229 или новее. |
CLAUDE_CODE_SESSION_ACCESS_TOKEN |
Токен доступа сессии для вызовов API в области сессии |
GIT_CONFIG_COUNT, GIT_CONFIG_KEY_n, GIT_CONFIG_VALUE_n |
Настройки git, которые runner фиксирует для git, запускаемого вашим хуком. Они описаны в разделе Конфигурация git внутри хуков жизненного цикла. Требуется Claude Code v2.1.280 или новее. |
CLAUDE_RUNNER_EXIT_REASON принимает одно из четырёх значений:
completed: сессия завершилась чисто. Процесс Claude Code завершился нормально или завершился сам после того, как сессия была архивирована или удалена.failed: процесс Claude Code упал, или настройка не удалась после его запуска.interrupted: runner остановил сессию в одном из следующих случаев:- Runner освободил сессию, чтобы освободить слот.
- Истекло время ожидания сессии при запуске.
- Сервер переместил сессию с этого runner'а.
- Опрос runner'а обнаружил архивирование или удаление до завершения процесса.
- Runner был в режиме дренирования.
- Сессия превысила свой лимит
--kill-session-after-min.
abandoned: зарезервировано для сессии, которую заявил другой runner. Hook в настоящее время не срабатывает в этом случае.
Если вы сравниваете квитанции хука со счётчиками жизненного цикла сессии, ожидайте, что некоторые квитанции interrupted будут учтены там как completed. Счётчики считают освобождение, истечение времени при запуске, перемещение сервером, а также архивирование или удаление, которые первым обнаружил опрос runner'а, как completed, потому что runner чисто вернул слот.
Статус выхода hook'а никогда не влияет на результат сессии; отказ логируется и игнорируется. Runner ждёт до --post-session-hook-timeout-sec, 60 секунд по умолчанию, при каждом завершении сессии, включая завершение runner'а. Этот пример сохраняет незафиксированную работу в ветку спасения:
#!/usr/bin/env bash
set -u
export GIT_ALLOW_PROTOCOL=${GIT_ALLOW_PROTOCOL:-https:http:ssh}
IFS=':'
# -c overrides beat repo-local settings, blocking session-written fsmonitor,
# hook-path, and gpg-program config from executing code with the hook's
# privileges. -c commit.gpgsign=false also leaves these rescue commits
# unsigned under --configure-git.
# Repo-local credential.helper and pushurl still apply, and on a runner
# before v2.1.280 so does core.sshCommand; see the note below the script
# before you give this push a credential.
g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
-c commit.gpgsign=false "$@"; }
for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
cd "$ws" 2>/dev/null || continue
[ -z "$(g status --porcelain 2>/dev/null)" ] && continue
g add -A
g commit -q -m "runner snapshot: $CLAUDE_RUNNER_SESSION_ID ($CLAUDE_RUNNER_EXIT_REASON)" || continue
g push -q origin "HEAD:refs/heads/rescue/$CLAUDE_RUNNER_SESSION_ID" || true
done
Строка GIT_ALLOW_PROTOCOL в скрипте ограничивает git удалёнными репозиториями по HTTPS, HTTP и SSH. Если окружение runner'а уже задаёт собственный непустой список GIT_ALLOW_PROTOCOL, скрипт сохраняет этот список.
Хук выполняет push с любыми учётными данными git, доступными в его собственном окружении на хосте runner'а. При подходе без учётных данных в образе, в том числе когда встроенное клонирование проходит через git-прокси Anthropic, их нет, поэтому перед push создайте краткосрочные учётные данные для отправки внутри хука: обменяйте токен сессии, который хук получает в CLAUDE_CODE_SESSION_ACCESS_TOKEN, в вашей собственной службе токенов, предварительно проверив его, как описано в разделе Проверка идентичности сессии.
Относитесь к любым учётным данным, которые ваш хук передаёт git, как к таким, которые может получить сессия, и создавайте их так, чтобы с их помощью можно было выполнить только этот push. Git в вашем хуке читает файлы конфигурации, в которые может записывать сессия, а помощник учётных данных или драйвер фильтра, указанный в одном из них, запускается с привилегиями вашего хука. Настройки в этих файлах также могут изменить, куда уходит push, независимо от того, какой удалённый репозиторий вы указываете. О настройках git, которые runner фиксирует в вашем хуке, и о тех, которые он оставляет на усмотрение этих файлов, см. раздел Конфигурация git внутри хуков жизненного цикла.
Hook timing when the runner releases a session
Освобождённая сессия может возобновиться на другом runner'е. На runner'е версии 2.1.236 или позже то, что сессия делала при освобождении, решает, может ли она возобновиться до завершения этого hook'а:
- Неактивна после хода или истекла при запуске: runner останавливает дочерний процесс и запускает этот hook до завершения. Только после этого он освобождает сессию. Сообщение пользователя, отправленное во время выполнения hook'а, не может возобновить сессию на другом runner'е до завершения hook'а.
- Ожидание ответа пользователя на подсказку, такую как подсказка разрешения: runner сначала освобождает сессию, затем запускает этот hook. Сообщение пользователя, отправленное во время выполнения hook'а, может возобновить сессию на другом runner'е до завершения hook'а.
Это применяется всякий раз, когда runner освобождает сессию: при истечении времени неактивности, при --retire-at времени и, на runner'е версии 2.1.260 или позже, при лимите --kill-session-after-min сессии. Сессия, чей ход закончился и которая содержит только фоновые задачи, считается неактивной здесь. До v2.1.236 runner сначала освобождал сессию, а затем запускал этот hook в обоих случаях.
Во время дренирования SIGTERM runner держит аренду сессии до завершения hook'а; см. Shutdown timing.
Git configuration inside lifecycle hooks
Хуки checkout и post-session запускаются с токеном доступа сессии в своём окружении, а git, который они запускают, читает файлы конфигурации, в которые могут записывать сессии, например ~/.gitconfig и .git/config в checkout. Перед запуском любого из этих хуков runner устанавливает в окружении хука настройки git, включая перечисленные ниже, в виде пар GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n и переменных окружения git. Git ставит их выше любого файла конфигурации, и они применяются только к git, который запускают ваши хуки, но не к собственному git сессии. При запуске runner выводит строку [runner:git] lifecycle hooks:, в которой показаны действующие путь к хукам, разрешённые протоколы, программы gpg и режим подписи. Требуется Claude Code v2.1.280 или новее.
- Хуки git: если вы не укажете значение,
core.hooksPathравен/dev/null, поэтому git пропускает хуки в.git/hooksрепозитория и любую директорию хуков, указанную в~/.gitconfig. Чтобы указать своё значение, экспортируйтеcore.hooksPathкак паруGIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_nв окружении runner'а. Runner также читаетcore.hooksPathиз системной конфигурации git и использует его, только если пользователь runner'а не может записывать в этот файл, в указанную в нём директорию или в файлы хуков в ней. Когда runner игнорирует значение, строка[runner:warn]при запуске называет это значение и причину. - Монитор файловой системы:
core.fsmonitorпуст, поэтому git в вашем хуке не запускает программу-монитор, указанную в файле конфигурации. - Удалённые протоколы:
GIT_ALLOW_PROTOCOLравнаhttps:http:ssh. Clone, fetch или push с локальным путём, URLfile://или URLgit://завершается ошибкойfatal: transport 'file' not allowedилиfatal: transport 'git' not allowed. - Команда SSH и запрос учётных данных: git в вашем хуке игнорирует
core.sshCommandиcore.askPassиз файлов конфигурации. Чтобы использовать собственную команду SSH, установитеGIT_SSH_COMMANDв окружении runner'а. Чтобы использовать программу запроса учётных данных, установите тамGIT_ASKPASS. Сессии наследуют окружение runner'а, поэтому обе переменные также попадают в собственный git сессии. Не помещайте учётные данные ни в одну из них. - Программы gpg:
gpg.program,gpg.openpgp.program,gpg.x509.programиgpg.ssh.program— это пути, которые устанавливает runner, а не значения из файла конфигурации. - Подпись коммитов: с
--configure-gitкоммиты, которые вы делаете из хука, подписываются от имени сессии. Без этого флагаcommit.gpgsignиtag.gpgsignравныfalse.
Чтобы изменить одну из этих настроек, используйте окружение runner'а или опцию git -c внутри хука:
- Пары конфигурации: пара
GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n, которую вы экспортируете в окружении runner'а, заменяет значение runner'а для того же ключа. Нумеруйте свои пары с0и установитеGIT_CONFIG_COUNTравной их количеству. Если последняя пара, заявленная этим количеством, отсутствует, runner игнорирует все ваши пары и при запуске записывает в лог строку[runner:warn]. - Переменные окружения git: runner оставляет
GIT_ALLOW_PROTOCOL,GIT_SSH_COMMANDиGIT_ASKPASSтакими, какими вы их установили в его окружении. - Опции
git -c: опцияgit -cвнутри хука переопределяет паруGIT_CONFIG_KEY_n— как runner'а, так и вашу. Она не изменяетGIT_ALLOW_PROTOCOL,GIT_SSH_COMMANDилиGIT_ASKPASS, которые git читает раньше любой конфигурации.
Git в вашем хуке по-прежнему читает все настройки, которые не устанавливает runner, например помощники учётных данных, перезаписи url.*.insteadOf и драйверы фильтров, из всех файлов конфигурации, включая те, в которые могут записывать сессии. Помощник учётных данных или драйвер фильтра, указанный в одном из этих файлов, запускается как программа с привилегиями вашего хука, а конфигурация в этих файлах по-прежнему может изменить, куда уходит push из вашего хука, в том числе push на URL, который вы передаёте в командной строке.
До v2.1.280 runner не устанавливал ни одну из этих настроек, а при --configure-git коммит из хука завершался ошибкой, если хук не передавал -c commit.gpgsign=false.
command
Запускается после checkout каждый раз, когда сессия запускается или возобновляется, вместо встроенного порождения дочернего процесса. Хук получает то же окружение, что и wrapper script, и должен exec в "$CLAUDE_RUNNER_CLAUDE_BIN" таким же образом. Используйте хук command, чтобы держать всю настройку в одной директории хуков; используйте --exec-path, когда wrapper находится в другом месте. Если --exec-path также установлен, флаг имеет приоритет и хук command игнорируется.
Всегда exec собственный бинарный файл runner'а вместо разрешённого PATH claude; иначе вы нарушите закрепление версии.
Средства выполнения по требованию
Вместо запуска фиксированного флота вы можете загрузить одно средство выполнения за сеанс. Оркестратор — это отдельная, без состояния подкоманда, которая опрашивает Anthropic на предмет запросов на порождение, по одному за сеанс, который находится в очереди без доступного средства выполнения, и запускает ваш хук spawn-runner для каждого. Ваш хук отправляет рабочую нагрузку на вашу платформу: Kubernetes Job, экземпляр EC2, Nomad dispatch.
Средства выполнения по требованию улучшают гигиену учетных данных. На фиксированном флоте секрет окружения находится на каждом хосте средства выполнения, который является тем же хостом, на котором запускается код пользователя. С оркестратором секрет окружения остается только на хосте оркестратора, который никогда не запускает код пользователя; каждое порожденное средство выполнения получает одноразовый наряд на работу, который регистрирует ровно одно средство выполнения и затем истекает.
Чтобы запустить оркестратор, передайте секрет окружения и каталог хуков, содержащий исполняемый скрипт spawn-runner:
claude self-hosted-runner orchestrator \
--environment-secret-file /etc/claude/environment-secret \
--hooks-dir /etc/claude/hooks
Оркестратор не сохраняет состояние между опросами, поэтому вы можете запустить две или более реплик против одного и того же окружения для доступности. Каждый запрос на порождение заявляется на стороне сервера ровно одной репликой. Все реплики должны использовать одно и то же значение --expected-spawn-seconds; см. контракт хука.
Хук spawn-runner
Оркестратор запускает ${hooks-dir}/spawn-runner один раз за запрос на порождение. Хук должен отправить работу асинхронно, без ожидания загрузки средства выполнения, и вернуться в течение --hook-timeout, 60 секунд по умолчанию. Хук получает:
| Переменная | Описание |
|---|---|
CLAUDE_RUNNER_WORK_ORDER_FILE |
Путь к временному файлу, содержащему подписанный JWT наряда на работу, с которым регистрируется новое средство выполнения. Удалено после выхода хука. Не логируйте содержимое файла. |
CLAUDE_RUNNER_ORDER_ID |
Непрозрачный ключ идемпотентности, уникальный для каждого запроса на порождение и безопасный для имен ресурсов Kubernetes. Используйте только ID заказа как ключ дедупликации вашего провизионера. |
CLAUDE_RUNNER_SESSION_ID |
Сессия, для которой предназначен этот запрос, в виде ID session_... из URL сессии. Он повторяется при каждом повторном запросе для сессии, поэтому используйте его для логирования и маршрутизации, а не как ключ дедупликации. Пусто для запросов предварительного прогрева, которые загружают резервное средство выполнения перед любой конкретной сессией, когда установлен --min-idle, поэтому не предполагайте, что переменная установлена. |
CLAUDE_RUNNER_SESSION_UUID |
Тот же ID сеанса в канонической форме UUID. Пусто для запросов предварительного прогрева. |
CLAUDE_RUNNER_ATTEMPT |
Счётчик для каждой сессии, предназначенный для логирования. Это не число повторных попыток и не число запросов. 0 для запросов предварительного прогрева, хотя запрос для сессии тоже может содержать 0. |
CLAUDE_RUNNER_ORDER_SERVER_TIME |
Время сервера из заголовка HTTP Date ответа опроса. Когда хук проверяет exp JWT наряда на работу, сравнивайте с этим значением вместо локальных часов, чтобы допустить перекос. Пусто, когда шлюз опустил заголовок. |
CLAUDE_RUNNER_POOL_ID |
ID окружения, к которому должно присоединиться новое средство выполнения, в форме ccpool_... |
CLAUDE_RUNNER_ACCOUNT_ID |
Помеченный ID учётной записи, которая поставила сессию в очередь. Используйте его для маршрутизации по учётной записи, квот или возврата средств, а не как ключ дедупликации. Пусто, когда недоступно. |
CLAUDE_RUNNER_ACCOUNT_EMAIL |
Электронная почта учетной записи, которая поставила сеанс в очередь. Пусто, когда недоступно. Рассматривайте электронную почту как личную информацию и не логируйте ее. |
CLAUDE_RUNNER_PRIMARY_REPO_URL |
URL первого источника git сеанса для маршрутизации на средство выполнения с этим репозиторием предварительно прогретым. Пусто, когда сеанс не имеет источников git. |
CLAUDE_RUNNER_PRIMARY_REPO_REVISION |
Ревизия первого git-источника сессии: ветка, SHA, тег или полное имя ссылки. Пусто, когда не указано. |
CLAUDE_RUNNER_REPO_SOURCES |
JSON массив {url, revision} для всех источников git сеанса для хуков, которые маршрутизируют на вторичный репозиторий. Пусто, когда нет источников. |
CLAUDE_RUNNER_CORRELATION_ID |
ID корреляции, предоставленный при создании сеанса, повторно отправленный, чтобы хук мог сопоставить этот наряд на работу с запросом, который создал сеанс. Пусто, когда сеанс не имеет ни одного. |
CLAUDE_RUNNER_CLIENT_PLATFORM |
Поверхность клиента, которая создала сеанс, такая как web_claude_ai, desktop_app, ios или scheduled_trigger, для аналитики внедрения. Не установлена, когда сеанс не имеет записанной или распознанной поверхности, и для запросов предварительного прогрева; проверьте ее с помощью [ -n "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" ], что остается безопасным под set -u. |
Порожденное средство выполнения регистрируется с нарядом на работу вместо секрета окружения:
- Запустите его с нарядом на работу: укажите
--environment-secret-fileна файл, содержащий JWT наряда на работу, или установитеSELF_HOSTED_RUNNER_ENVIRONMENT_SECRETна значение JWT. - Скопируйте JWT перед выходом хука: оркестратор удаляет файл наряда на работу после выхода хука, поэтому скопируйте JWT в рабочую нагрузку, которую вы отправляете, такую как Kubernetes Secret на порожденном Job, вместо передачи пути файла.
- Используйте
--capacity 1на порожденных средствах выполнения: наряд на работу, привязанный к сеансу, регистрирует ровно одно средство выполнения, привязанное к этому сеансу, поэтому более высокая емкость добавляет слоты, которые никогда не получают работу, и средство выполнения логирует предупреждение при запуске. - Нарядные работы предварительного прогрева регистрируют без привязки: резервное средство выполнения не привязано к сеансу и заявляет поставленную в очередь работу, как средство выполнения фиксированного флота.
Контракт состоит из четырёх правил, независимо от того, на какой платформе ваш хук выделяет ресурсы:
-
Будьте идемпотентны по
CLAUDE_RUNNER_ORDER_ID. Повторная доставка одного и того же запроса должна порождать не более одного средства выполнения. Выведите детерминированное имя ресурса из ID заказа и позвольте вашей платформе отклонить дубликат. Не используйте вместо этогоCLAUDE_RUNNER_SESSION_IDв качестве ключа. Каждый повторный запрос для сессии несёт тот же ID сессии с новым ID заказа. Рабочая нагрузка, названная или дедуплицированная по ID сессии, не создаётся снова, пока существует первая. -
Не повторяйте рабочую нагрузку. Один ID заказа означает не более одной созданной рабочей нагрузки. Если средство выполнения никогда не регистрируется, Anthropic повторно запрашивает с новым ID заказа после
--expected-spawn-seconds. -
Соблюдайте контракт кода выхода. Завершайтесь со статусом, соответствующим результату:
- Выход 0: отправлено.
- Выход 1 или хук всё ещё выполняется по истечении
--hook-timeout: сбой, допускающий повторную попытку. Сессия выжидает и предлагается снова. После 10 неудачных запросов подряд она блокируется так же, как после выхода 2 или выше. - Выход 2 или выше: сбой, не допускающий повторной попытки. Сессия блокируется от повторного порождения до тех пор, пока пользователь не отправит ей новое сообщение или Owner не выберет для неё Retry на вкладке Activity окружения.
Пока сессия выжидает, она отображается на вкладке Activity как Queued без ошибки порождения, поэтому читайте вывод хука в логе оркестратора. После блокировки сессия отображается как Failed. Разверните её, чтобы прочитать ошибку порождения, взятую из последних 512 символов stderr хука. Пишите в stderr понятную ошибку, указывающую, что делать, и никаких секретов. В shell-хуке сохраняйте временные сбои повторяемыми.
У запроса предварительного прогрева нет сессии, которая могла бы завершиться сбоем: оркестратор записывает ненулевой выход только в локальный лог, а сервер повторно запрашивает порождение после истечения аренды
--expected-spawn-seconds. -
Установите
--expected-spawn-secondsне меньше вашего времени p99 от запроса на порождение до регистрации средства выполнения. Отсчитывайте от момента, когда оркестратор получает запрос на порождение, и учитывайте как ожидание ресурсов на вашей платформе, так и время загрузки. Это значение является арендой на стороне сервера, и наряд на работу истекает вместе с ней, поэтому средство выполнения, рабочая нагрузка которого запускается дольше, не сможет зарегистрироваться. Все реплики оркестратора должны использовать одно и то же значение.
Все, что хук пишет в stdout или stderr, появляется в логе оркестратора с автоматически удаленными учетными данными. Если сеансы остаются в очереди, проверьте тело /healthz оркестратора на предмет количества в очереди, затем откройте вкладку Activity вашего окружения на странице администратора Cloud environments: разверните неудачный сеанс там для его ошибки порождения и выберите Retry для повторного запроса.
Исправьте повторно открытые сессии, которые остаются в статусе Queued или отображаются как Failed
Если повторно открытые сессии остаются в статусе Queued или отображаются как Failed, либо ваш хук завершается для них по таймауту, одна из возможных причин — хук, который называет или дедуплицирует рабочие нагрузки по ID сессии. Сессия открывается повторно, когда её пользователь отправляет ей сообщение после того, как её средство выполнения завершилось. Например, возьмём хук, который формирует имя каждой рабочей нагрузки так:
name="runner-${CLAUDE_RUNNER_SESSION_ID}"
- При первом запросе на порождение для сессии хук создаёт рабочую нагрузку с этим именем, и средство выполнения запускается
- Средство выполнения завершается, например после
--release-idle-session-min, а позже пользователь повторно открывает сессию - Следующий запрос на порождение для сессии содержит тот же ID сессии и новый ID заказа, поэтому хук формирует то же имя
- Если на вашей платформе всё ещё есть рабочая нагрузка от первого запроса, потому что ничто её не удалило при завершении её средства выполнения, хук не создаёт новую рабочую нагрузку, и средство выполнения не запускается
- Каждый последующий запрос на порождение для сессии заканчивается так же
Откройте свой хук и посмотрите, что он делает, когда рабочая нагрузка уже существует, например когда ваша платформа отклоняет дублирующееся имя. Найдите это поведение в первом столбце, чтобы узнать, что повторно открытая сессия показывает на вкладке Activity:
| Когда рабочая нагрузка существует, ваш хук | Queued меняется на Failed | Ошибка порождения |
|---|---|---|
| Завершается с кодом 0, ничего не создав | После 10 таких запросов подряд | runner did not register within ETA after max attempts |
| Завершается с кодом 1 | После 10 таких запросов подряд | Последние строки stderr вашего хука или exit 1, если он ничего не записал в stderr |
| Завершается с кодом 2 или выше | Сразу | Последние строки stderr вашего хука или exit и код, например exit 2, если он ничего не записал в stderr |
Ждёт, пока не истечёт --hook-timeout |
После 10 таких запросов подряд | Последние строки stderr вашего хука или spawn-runner hook timed out after 60000ms, если он ничего не записал в stderr |
В ошибке порождения из последней строки 60000 — это ваш --hook-timeout в миллисекундах. Когда хук ждёт, пока не истечёт --hook-timeout, в лог оркестратора также попадает строка timed out after.
Чтобы исправить хук, формируйте каждое имя или ключ из CLAUDE_RUNNER_ORDER_ID, как требует правило 1 контракта хука. В примере имя становится таким:
name="runner-${CLAUDE_RUNNER_ORDER_ID}"
Если ваш хук ждёт, также сделайте так, чтобы он завершался после отправки рабочей нагрузки, потому что ожидающий хук задерживает следующие хуки своего оркестратора. После изменения хука проверьте статус каждой зависшей сессии на вкладке Activity:
- Queued: сессия получит следующий запрос на порождение самостоятельно
- Failed: выберите для неё Retry как Owner или попросите её пользователя отправить ей новое сообщение
Настройте завершение средств выполнения по требованию при простое
По умолчанию средство выполнения, запущенное вашим хуком, не завершается, когда заканчивается ход его сессии, поэтому отправленная вами рабочая нагрузка продолжает работать. Чтобы средства выполнения завершались, пусть ваш хук передаёт следующие флаги средства выполнения:
| Флаг с примером значения | Кому передавать | Что он делает |
|---|---|---|
--release-idle-session-min 30 |
Каждому средству выполнения, которое запускает ваш хук | Средство выполнения освобождает свою сессию после указанного числа минут простоя, а затем завершается с кодом 0. Сессия возобновляется, когда её пользователь отправляет следующее сообщение, но работа, которую она не успела отправить через push, теряется. Удерживайте сессию для процессов, которые она оставила запущенными. |
--kill-session-after-min 480 |
Каждому средству выполнения, которое запускает ваш хук | Ограничивает каждую сессию указанным числом минут в качестве страховки для сессий, которые не считаются простаивающими, поэтому задайте значение больше самой длинной ожидаемой сессии |
--exit-if-unused-min 60 |
Резервным средствам выполнения, которые ваш хук запускает для запросов предварительного прогрева | Резервное средство выполнения, которое так и не получило сессию, завершается через указанное число минут. Если вы задали --min-idle, вы получите замену, если только резервное средство не было избыточным, поэтому используйте значение значительно больше времени загрузки. |
Удаляйте рабочую нагрузку, когда средство выполнения завершается, например задав ttlSecondsAfterFinished для Kubernetes Job или настроив стартовый скрипт VM так, чтобы он затем удалял VM.
Направляйте вывод фонового процесса в файл
Если ваш хук spawn-runner оставляет процесс работать в фоне, направляйте вывод этого процесса в файл, который может создать пользователь хука. Например, добавьте >> "$HOME/claude-runner.log" 2>&1 к команде, запускающей процесс, перед любым &. Эта строка хука показывает, где находится перенаправление, где your-background-command заменяет команду, которую ваш хук запускает в фоне:
your-background-command >> "$HOME/claude-runner.log" 2>&1 &
Без перенаправления процесс пишет в stdout и stderr хука:
- Примерно через секунду после выхода хука оркестратор записывает в лог
exited, but a process it started still has the hook's stdout or stderr open. Начиная с этой строки, он записывает в лог до 64 КБ последующего вывода под[runner:hook:spawn-runner:after-exit jti=<order id>], а остальное отбрасывает. - После завершения оркестратора запись процесса туда завершается сбоем.
Средство выполнения, которое ваш хук запускает сам, выполняет сессии на хосте оркестратора, где хранится секрет окружения.
До версии v2.1.305 хук, который уже завершился, всё равно достигал --hook-timeout, пока запущенный им процесс держал открытыми stdout или stderr хука, что отображалось так:
- Вкладка Activity, в ошибке порождения неудачной сессии:
spawn-runner hook timed out after 60000msили последние строки stderr хука. - Лог оркестратора:
timed out after 60000ms, sending SIGTERM to process tree.
Завершайте хук, не дожидаясь средства выполнения
Если каждое средство выполнения по требованию останавливается примерно через 60 секунд после запуска его хука, а ваш хук снова запускается для той же сессии, проверьте, не ждёт ли хук средство выполнения. 60 секунд — это значение --hook-timeout по умолчанию, поэтому если вы задали собственное, средства выполнения останавливаются через это время после запуска хука.
Хук, который запускает claude self-hosted-runner на хосте оркестратора и ждёт его, на переднем плане или с помощью wait, всё ещё выполняется, когда истекает --hook-timeout. Тогда оркестратор останавливает хук и процессы, выполняющиеся под ним, включая это средство выполнения и процесс Claude сессии, поэтому ещё не завершившийся ход обрывается.
- Чтобы убедиться, что ваши средства выполнения останавливает именно таймаут: найдите в логе оркестратора
timed out after 60000ms, sending SIGTERM to process treeдля каждого запроса на порождение.60000— это ваш--hook-timeoutв миллисекундах. - Чтобы исправить это: пусть хук отправляет средство выполнения на вашу платформу и завершается с кодом 0. Если вместо этого увеличить
--hook-timeout, средство выполнения просто остановится позже.
Сохраняйте временные сбои повторяемыми в shell-хуке
В shell-хуке, использующем set -e, сбой, который могла бы устранить повторная попытка, может заблокировать сессию. Хук останавливается на сбойной команде и завершается с собственным статусом этой команды, а оркестратор применяет контракт кода выхода к этому статусу. Многие сбои возвращают статус 2 или выше, например 127, когда команда не установлена, и 22 от curl --fail при ошибке HTTP, поэтому они блокируют сессию при первом же сбое.
Сессия, которую хук уже заблокировал, остаётся заблокированной, пока пользователь не отправит ей новое сообщение или Owner не выберет для неё Retry на вкладке Activity окружения.
Чтобы вместо этого превращать такой сбой в выход 1, поместите эти строки сразу под строкой #! хука, перед всем, что может завершиться сбоем:
set -e
PERMANENT=; permanent() { printf '%s\n' "$*" >&2; PERMANENT=1; exit 2; }
trap 'rc=$?; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT
Эти строки меняют поведение остальной части хука, поэтому после их добавления проверьте хук на каждый из следующих шаблонов:
-
Просто
exit 2или выше: при установленной ловушке он превращается в выход 1. Для ошибки, которую не исправит никакая повторная попытка, вместо этого вызовитеpermanentс указанием причины, напримерpermanent "namespace claude-runners does not exist". Вызывайте её в основной оболочке, а не внутри$( ),( )или конвейера. -
exec: не начинайте последнюю команду хука сexec, потому чтоexecзаменяет оболочку, и ловушка не выполняется. -
Вторая ловушка
EXIT: второйtrap ... EXITзаменяет первый, поэтому объедините их в одну ловушку. Поместите команды очистки сразу послеrc=$?;и завершите каждую|| true;. Тогда очистка выполняется как при сбое, так и при успехе, а сбойная команда очистки не влияет на статус выхода хука. Эта объединённая ловушка показывает структуру, гдеyour-cleanup-commandзаменяет вашу собственную команду:trap 'rc=$?; your-cleanup-command || true; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT -
Команды, которым разрешено завершаться сбоем: если хук раньше не использовал
set -e, теперь он останавливается на первой команде, вернувшей ненулевой статус, например на поиске, который ничего не нашёл. Если хук использует результат, сделайте эту команду условиемif. Если он игнорирует результат, добавьте после команды|| true. -
Команда, которая отправляет рабочую нагрузку: не добавляйте после неё
|| true. С|| trueхук завершается с кодом 0, даже когда ваша платформа отклоняет отправку, например из-за превышения квоты, и сессия ждёт средство выполнения, которое так и не было запущено.Называйте рабочую нагрузку по
CLAUDE_RUNNER_ORDER_ID, как требует первое правило хука. Тогда ответ «уже существует» означает повторную доставку того же запроса, поэтому считайте успехом только этот ответ. Во всех остальных случаях выведите ответ в stderr и завершитесь с кодом 1 или вызовитеpermanent, если никакая повторная попытка не поможет. Этот пример делает это дляkubectl createс файломjob.yaml, который содержит только рабочую нагрузку:if ! out=$(kubectl create -f job.yaml 2>&1); then case "$out" in *AlreadyExists*) ;; *) printf '%s\n' "${out:-kubectl create failed}" >&2; exit 1 ;; esac fiТогда в логе оркестратора отображается это сообщение. Если хук создаёт другие объекты, называйте каждый по ID заказа, создавайте его отдельной командой и так же считайте успехом его ответ «уже существует». Для объекта, содержащего секрет, выводите фиксированное сообщение вместо вывода команды.
Чтобы убедиться, что ловушка работает, добавьте сразу под строкой trap строку, вызывающую несуществующую команду, например no-such-command. Запустите файл хука из своей оболочки и убедитесь, что echo $? выводит 1, затем удалите эту строку.
Узнайте, почему был отправлен запрос на порождение
Ваш хук получает причину каждого запроса на порождение в CLAUDE_RUNNER_SPAWN_REASON, поэтому вы можете логировать, почему хук был запущен, вместе с CLAUDE_RUNNER_SESSION_UUID. Требуется Claude Code v2.1.318 или новее на оркестраторе. Читайте её как "${CLAUDE_RUNNER_SPAWN_REASON:-}", потому что она может быть не установлена, и запускайте средство выполнения при любом значении. Если ваш хук ветвится по ней, оставьте ветку по умолчанию, потому что Anthropic может добавлять новые значения.
Чтобы найти причину более ранних запросов, ищите в логе каждой реплики оркестратора session= и CLAUDE_RUNNER_SESSION_UUID сессии, а не её ID session_.... Строки ok session= и nack session= показывают значение CLAUDE_RUNNER_SPAWN_REASON после reason= или reason=-, если оно не было установлено:
[runner:orchestrator] ok session=: хук завершился с кодом 0[runner:orchestrator] nack session=: оркестратор сообщает Anthropic о неудачном запросе на порождение
Каждое значение передаёт только последнюю причину, которую Anthropic записал для сессии, а не её историю:
| Значение | Что произошло | Что проверить |
|---|---|---|
session_created |
Это первый запрос на порождение для новой сессии | Ничего. Не полагайтесь на него, чтобы распознать первый запрос сессии, потому что первый запрос, который видит ваш хук, может содержать runner_lost, если сессию удерживало средство выполнения фиксированного флота или резервное средство выполнения. |
no_runner_registered |
Ни одно средство выполнения не зарегистрировалось для предыдущего запроса в пределах --expected-spawn-seconds, и Anthropic не записал для него отчёта о сбое |
stop: no nack for session= или nack failed session= среди более ранних строк сессии означает, что отчёт о сбое не был записан. Прочитайте любую ошибку хука перед ней, а если оркестратор останавливался, см. Остановка или перезапуск оркестратора. После строки ok session= проверьте, что её средство выполнения запустилось и зарегистрировалось в пределах --expected-spawn-seconds. Если ни одна реплика не записала запрос в лог, проверьте свои оркестраторы на перезапуски и строки poll failed. |
hook_failed |
Ваш оркестратор сообщил, что предыдущий запрос завершился сбоем, допускающим повторную попытку, например потому что ваш хук завершился с кодом 1 или всё ещё выполнялся, когда истёк --hook-timeout. Значение то же, когда ваш оркестратор возвращает запрос Anthropic, не запуская хук, например во время остановки оркестратора. |
Найдите последнюю строку сессии [runner:orchestrator] nack session= или stop: handing back session= перед этим запросом. Ошибка порождения, которую отправил оркестратор, следует за значением reason= и может продолжаться на следующих строках. Строка handing back session= означает, что оркестратор останавливался и не запускал хук, поэтому в хуке исправлять нечего. |
runner_lost |
Средство выполнения, которое удерживало сессию, перестало отчитываться перед Anthropic, отменило регистрацию или было удалено | Найдите, что остановило средство выполнения или отрезало его от Anthropic, например вытеснение узла, завершение из-за нехватки памяти, изъятие spot-экземпляра или ваше собственное развёртывание или уменьшение масштаба |
runner_reported_failure |
Предыдущее средство выполнения сообщило, что сессия на нём завершилась сбоем, например во время checkout | Прочитайте лог предыдущего средства выполнения. Если это значение получают многие сессии, проверьте образ и настройку репозитория. |
resumed |
Сессия была приостановлена и теперь снова нуждается в средстве выполнения. Например, её средство выполнения освободило её согласно --release-idle-session-min, а позже она получила новое сообщение. |
Ничего |
requeued |
Сессия была возвращена в очередь, например когда Owner выбрал Retry или заблокированная сессия получила новое сообщение | Если одна и та же сессия продолжает получать это значение, прочитайте ошибку в её строках nack session=. Если её хук завершился с кодом 0, прочитайте лог средства выполнения. |
server_retry |
Anthropic отказался от предыдущего запроса или от запуска сессии на её средстве выполнения и повторяет попытку. Ваш хук не завершился сбоем. | Ничего для единичного запроса. Если значение продолжает появляться, для одной сессии или для многих, сообщите об этом вашей команде по работе с клиентами Anthropic. |
prewarm |
Это запрос предварительного прогрева, сессии ещё нет | Ничего |
Остановка или перезапуск оркестратора
Чтобы остановить или перезапустить оркестратор, не обрывая выполняющиеся хуки spawn-runner, запускайте его с --shutdown-drain-sec. Тогда при получении SIGTERM он даёт выполняющимся хукам на завершение не больше заданного вами числа секунд. О том, как выбрать значение и настроить под него супервизор процессов, см. Подбор --shutdown-drain-sec и таймаута остановки оркестратора.
Эта команда запуска отводит 70 секунд:
claude self-hosted-runner orchestrator \
--environment-secret-file /etc/claude/environment-secret \
--hooks-dir /etc/claude/hooks \
--shutdown-drain-sec 70
Оркестратор завершается, как только завершатся уже отправленный опрос и все хуки. Когда истекают секунды --shutdown-drain-sec, хуки, которые всё ещё выполняются, получают SIGTERM, и оркестратор завершается. Второй SIGTERM или любой SIGINT приводит к тому же без ожидания. Строка лога, содержащая [runner:orchestrator] stop summary:, подсчитывает хуки и запросы на порождение по тому, как завершился каждый из них.
Без --shutdown-drain-sec оркестратор останавливается сразу при получении SIGTERM или SIGINT. Каждый выполняющийся хук получает SIGTERM, а Anthropic ничего не сообщается. Сессия, средство выполнения которой так и не было запущено, может тогда оставаться в очереди, пока Anthropic не запросит его повторно, что происходит по истечении --expected-spawn-seconds, по умолчанию 120 секунд.
[!NOTE]
--shutdown-drain-secтребует Claude Code v2.1.299 или новее. Более старая версия завершается при запуске с ошибкойerror: unknown flag --shutdown-drain-sec.
Подбор --shutdown-drain-sec и таймаута остановки оркестратора
При настройках по умолчанию используйте --shutdown-drain-sec 70 с таймаутом остановки 85 секунд, то есть terminationGracePeriodSeconds: 85 в Kubernetes или TimeoutStopSec=85 в systemd. Если вы изменили --hook-timeout, масштабируйте оба значения:
--shutdown-drain-sec: не меньше--hook-timeoutплюс 10 секунд, чтобы хук, запущенный прямо передSIGTERM, всё же успел завершиться- Таймаут остановки: не меньше
--shutdown-drain-secплюс 15 секунд. Оркестратор записывает это значение в лог при запуске: найдите строку, содержащуюshutdown drain:.
Настройте супервизор процессов так, чтобы он отправлял один SIGTERM и только процессу оркестратора, потому что второй сигнал прерывает ожидание --shutdown-drain-sec. Если оставить в systemd значение по умолчанию KillMode=control-group, хуки тоже получат SIGTERM, и хук может завершиться до того, как отправит свою рабочую нагрузку. Прежде чем менять KillMode, проверьте, как ваш хук spawn-runner запускает средства выполнения:
- Если ваш хук отправляет рабочую нагрузку на платформу и завершается: запускайте оркестратор как основной процесс юнита и задайте
KillMode=mixed, при которомSIGTERMотправляется только этому процессу. - Если ваш хук запускает средства выполнения как процессы в собственном юните оркестратора: при
KillMode=mixedэти средства выполнения не получаютSIGTERM. Вместо этого systemd позже отправляет имSIGKILL, поэтому они завершаются, не освободив свои сессии и не выполнив свой хукpost-session.
Отправка запросов к модели в Bedrock или Agent Platform
Если ваша организация требует, чтобы запросы к модели проходили через её собственный аккаунт AWS или Google Cloud, настройте runner для Amazon Bedrock или Google Cloud's Agent Platform, ранее Vertex AI. После этого каждая сессия, которую запускает этот runner, обращается к модели в вашем облачном аккаунте с вашими облачными учётными данными. Без этой конфигурации сессии отправляют запросы к модели в Anthropic API.
Runner по-прежнему опрашивает Anthropic на предмет сессий, и каждая сессия по-прежнему отправляет свой поток событий на api.anthropic.com. Поток событий содержит промпты, ответы и результаты инструментов. Требование к тарифному плану и исключение для Zero Data Retention из раздела Доступность и ограничения по-прежнему действуют.
Сессии направляются в окружение, а не на конкретный runner, и повторно поставленная в очередь или возобновлённая сессия может выполняться на другом runner. Настройте все runner в окружении одинаково. Прежде чем начать, прочитайте, чем отличаются сессии у этих провайдеров.
1. Подготовьте облачный аккаунт и правила исходящего трафика
Настройте доступ к моделям, политику или роль с узкими правами и сетевой доступ:
- Amazon Bedrock: отправьте сведения о сценарии использования, затем создайте политику из раздела Конфигурация IAM, ограничив
bedrock:InvokeModelиbedrock:InvokeModelWithResponseStreamпрофилями инференса, которые используют ваши сессии, и базовыми моделями, стоящими за ними - Agent Platform: включите API и запросите доступ к моделям, затем создайте пользовательскую роль, описанную в разделе Конфигурация IAM, только с разрешением
aiplatform.endpoints.predict - Исходящий трафик: разрешите эндпоинты вашего провайдера в правилах исходящего трафика. См. Сетевые требования. Если сессии не могут до них достучаться, Claude Code может повторять попытки часами, прежде чем в сессии появится ошибка.
2. Выдайте сессиям учётные данные с узкими правами
Привяжите политику или роль из шага 1 к идентификатору, который больше ничего не может делать. Способы, которые поддерживает Claude Code, описаны в разделах Настройка учётных данных AWS и Настройка учётных данных GCP.
[!WARNING]
Любой, кто сможет запустить код в сессии, в том числе через промпт-инъекцию, сможет использовать эти учётные данные за ваш счёт, пока они действительны. Claude Code работает внутри сессии, поэтому учётные данные, с которыми он обращается к модели, должны быть там доступны для чтения.
Shell-команды, которые запускает Claude, ваши хуки Claude Code и stdio MCP-серверы наследуют окружение сессии и работают от имени того же пользователя, что и Claude Code. Поэтому они могут читать переменные и файлы с учётными данными.
Когда вы усиливаете защиту развёртывания, вы не допускаете учётные данные хоста в сессии, но эти учётные данные исключить нельзя. Не давайте стоящему за ними идентификатору ничего сверх политики или роли из шага 1.
Сверьте выбранный способ со следующими особенностями поведения runner:
- Эндпоинт метаданных: если вы полностью запрещаете сессиям доступ к облачному эндпоинту метаданных, учётные данные, выдаваемые через него, например профиль экземпляра, тоже не дойдут до Claude Code. Файловая веб-идентификация, например IAM Roles for Service Accounts (IRSA) в Amazon EKS или файл учётных данных Workload Identity Federation, от него не зависит.
- Обновление: сессия может пережить срок действия учётных данных, поэтому используйте способ с автоматическим обновлением, например файловую веб-идентификацию
- Скрипт-обёртка: значения учётных данных, которые экспортирует ваш скрипт-обёртка, не обновляются, пока работает Claude Code, поэтому для длительной сессии используйте способ с автоматическим обновлением. Claude Code читает учётные данные AWS из своего окружения, поэтому если ваша обёртка уже экспортирует учётные данные AWS для других задач, Claude Code может подписывать ими запросы к модели.
3. Задайте переменные одного провайдера в окружении runner
Задайте переменные ровно одного провайдера там же, где вы задаёте остальные переменные окружения runner, например в спецификации контейнера или в service unit, а затем перезапустите runner. В примерах они показаны как shell-экспорты. При использовании runner по требованию задайте их для рабочей нагрузки, которую запускает ваш хук spawn-runner.
Запускайте эти runner с --confine-repo-settings enforce. Этот режим отклоняет сессии в репозиториях, закоммиченные настройки которых он помечает, поэтому сначала запустите runner в режиме по умолчанию warn и устраните то, что он записывает в лог.
[tab] Amazon Bedrock
Замените регион на свой:
export CLAUDE_CODE_USE_BEDROCK=1 export AWS_REGION=us-east-1О том, как Claude Code определяет регион, см. Настройка Claude Code. О том, какой префикс профиля инференса Claude Code использует для вашего региона, см. Префиксы межрегиональных профилей инференса.
[tab] Google Cloud's Agent Platform
Замените регион и идентификатор проекта на свои:
export CLAUDE_CODE_USE_VERTEX=1 export CLOUD_ML_REGION=global export ANTHROPIC_VERTEX_PROJECT_ID=YOUR-PROJECT-IDО выборе региона см. Конфигурация региона.
4. Убедитесь, что переменные дошли до сессии
Ваша собственная оболочка на хосте — это другой процесс, поэтому проверяйте изнутри сессии. Запустите сессию в окружении и попросите Claude выполнить эту команду:
env | grep -E 'CLAUDE_CODE_USE_(BEDROCK|VERTEX)'
Строка, в которой CLAUDE_CODE_USE_BEDROCK или CLAUDE_CODE_USE_VERTEX имеет значение 1, означает, что переменная дошла до сессии. Если присутствуют обе, Claude Code использует Amazon Bedrock. Отсутствие вывода означает, что ни одна из них не дошла.
Команда показывает конфигурацию, а не трафик. Чтобы подтвердить сами запросы, найдите их в метриках или логах запросов вашего облачного аккаунта. Если вместо этого первое сообщение завершается ошибкой, см. раздел об устранении неполадок для Amazon Bedrock или Agent Platform.
Чем отличаются сессии от сессий в Anthropic API
Сессия, отправляющая запросы к модели в Amazon Bedrock или Google Cloud's Agent Platform, отличается от сессии в Anthropic API следующим:
- Политики из claude.ai: настройки, управляемые сервером, не доходят до этих сессий. Не доходят и политики организации, которые Owner задаёт в настройках администратора Claude Code, поэтому Claude Code не применяет их внутри сессии. Поместите правила, на которые вы полагаетесь, в файл управляемых настроек образа runner.
- Скиллы аккаунта: эти сессии не загружают скиллы, включённые для аккаунта claude.ai пользователя. См. Как собирается конфигурация каждой сессии.
- Файлы: файлы, которые пользователи прикрепляют к сессии в claude.ai, мобильном или десктопном приложении, до неё не доходят, и Claude не может отправлять файлы обратно с помощью инструмента
SendUserFile. Вместо этого размещайте входные файлы в репозитории или на runner. - Выбор модели: управляющий уровень Anthropic передаёт модель для каждой сессии, а если сессия запускается без неё, Claude Code использует модель по умолчанию для провайдера. Вы не можете выбрать модель с помощью
ANTHROPIC_MODELилиANTHROPIC_DEFAULT_MODELв окружении runner, но можете закрепить, во что разрешается псевдоним:ANTHROPIC_MODELиANTHROPIC_DEFAULT_MODEL: runner удаляет их из окружения, которое он передаёт сессиям, хотя в примерах на страницах провайдеров задаётсяANTHROPIC_MODEL.- Переменные закрепления для отдельных семейств: переменные из раздела «Закрепление версий моделей» для Amazon Bedrock и Agent Platform до сессий доходят. Они определяют, во что разрешается псевдоним, например
opus, но не полный идентификатор модели.
- Модели, которые ваш аккаунт не обслуживает: сессия может завершиться ошибкой на сообщении с указанием модели. Включите модели, которые могут выбирать ваши разработчики, фоновую модель, описанную в разделе «Закрепление версий моделей», и модель классификатора, которую использует авторежим. В Amazon Bedrock разрешите каждую из них в своей политике.
- Веб-поиск и быстрый режим: веб-поиск недоступен в Amazon Bedrock, а быстрый режим недоступен ни у одного из этих провайдеров. О других возможностях, различающихся в зависимости от провайдера, см. Возможности CLI, зависящие от провайдера.
MCP-серверы
Чтобы MCP-серверы были доступны в каждой сессии, добавьте их на этапе сборки образа той же командой claude mcp add, что используется при установке на рабочий компьютер. Если ваш раннер работает как обычный процесс, а не в контейнере, выполните ту же команду на хосте от имени пользователя раннера, а затем перезапустите раннер: он считывает конфигурацию хоста один раз при запуске. Флаг --scope user обязателен: локальная область действия по умолчанию записывает данные под ключом для конкретного каталога, который раннер не передаёт в сессии. Например, в вашем Dockerfile:
RUN claude mcp add --scope user sidecar -- /usr/local/bin/mcp-sidecar
RUN claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080
Раннер делает снимок конфигурации хоста один раз при запуске. Снимок включает ключ mcpServers из файла .claude.json хоста, который находится рядом с ~/.claude/, а не внутри него, и раннер передаёт в изолированную конфигурацию каждой сессии только этот ключ; состояние учётной записи и история проектов отбрасываются. Чтобы убедиться, что серверы попали в сессии, запустите сессию в окружении и попросите Claude перечислить его инструменты MCP; кроме того, при запуске раннер записывает в лог предупреждение для каждой захваченной записи, type которой он не распознаёт, и отбрасывает такую запись, поэтому вы можете увидеть, почему этот сервер отсутствует в сессиях. Если задана переменная SELF_HOSTED_RUNNER_HOST_CONFIG_DIR, раннер считывает .claude.json из этого каталога, поэтому, указав в переменной пустой каталог, вы также отключите передачу MCP-серверов.
Claude Code также загружает MCP-серверы из других источников:
- Управляемый файл MCP корпоративного уровня по стандартному системному пути:
/etc/claude-code/managed-mcp.jsonна хостах раннеров под Linux,/Library/Application Support/ClaudeCode/managed-mcp.jsonна хостах под macOS. Используйте его для строго контролируемых парков машин, где могут загружаться только серверы из списка администратора. Правила приоритета описаны в разделе эксклюзивный контроль с помощью managed-mcp.json. Если этот файл находится на хосте раннера, Claude Code пропускает MCP-серверы, которые плоскость управления Anthropic передаёт в сессию, включая коннекторы claude.ai, и перечисляет их в предупреждении в stderr дочернего процесса сессии, которое раннер записывает в лог на уровнеdebug. До версии v2.1.229 такие сессии завершались при запуске с ошибкойYou cannot dynamically configure MCP servers when an enterprise MCP config is present. - Ключ
managedMcpServersв управляемых настройках на хосте раннера: предоставляет серверы HTTP и SSE без эксклюзивного контроля, поэтому серверы из других источников по-прежнему загружаются. Требуется Claude Code v2.1.259 или новее. <repo>/.mcp.json: область действия проекта. Вне треда проекта сделайте коммит этого файла в репозиторий, и его серверы будут автоматически подтверждаться в облачных сессиях. В сессии с несколькими репозиториями загружается файл не более чем из одного репозитория.- Файл
--mcp-configиз вашего скрипта-обёртки: добавьте флаг после"$@", напримерexec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.json. Чтобы серверы из этого файла продолжали работать, когда переданные в сессию серверы обновляются посреди сессии, ваша обёртка должна передавать дочернему процессу полное окружение, как это делаетexec. До версии v2.1.299 серверы stdio, HTTP и SSE из этого файла завершали работу при первом таком обновлении, в котором они не были указаны.
Если для вашей организации включена передача коннекторов, плоскость управления Anthropic передаёт коннекторы, настроенные вами на claude.ai, в сессии, созданные интерактивно, через предоставляемую сервером конфигурацию MCP, маршрутизируемую через api.anthropic.com. Сессии, созданные программно, например запуски через CLI, не получают коннекторы; вместо этого предоставьте им MCP-серверы через любой из других источников, перечисленных в этом разделе. OAuth-токен дочернего процесса не содержит scope для прямого получения коннекторов, поэтому дочерний процесс сам не пытается их получить; передача управляется сервером.
[!NOTE]
В сессии, получившей коннектор, сервер с тем же именем из
.claude.jsonхоста или из.mcp.jsonрепозитория не подключается. Чтобы использовать оба, дайте своему серверу другое имя.
settings.json не содержит определений MCP-серверов, и в схеме настроек нет поля mcpServers верхнего уровня. В управляемых настройках вместо этого задавайте серверы с помощью ключа managedMcpServers.
Сессии наследуют окружение раннера, поэтому задайте там ENABLE_TOOL_SEARCH, чтобы управлять поиском инструментов MCP для каждой сессии, которую запускает раннер; допустимые значения описаны на странице MCP.
Ожидание MCP-серверов перед первым ходом
Сессия на собственном хостинге ненадолго ожидает MCP-серверы, которые ещё подключаются, в двух разных точках. Если сервер не успевает к моменту окончания ожидания, его инструменты отсутствуют в начале первого хода и становятся доступны позже без каких-либо действий с вашей стороны. Вот эти две точки ожидания:
- Запуск сессии: до того как список инструментов будет получен в первый раз, сессия по умолчанию ждёт до 5 секунд сервер HTTP или SSE, в записи которого задано
alwaysLoad: true, или все серверы, если в окружении раннера заданоMCP_CONNECTION_NONBLOCKING=0. В остальных случаях серверы HTTP и SSE подключаются в фоновом режиме. Пока сессия ждёт на этом этапе, она инициализируется медленнее.MCP_CONNECT_TIMEOUT_MSизменяет значение по умолчанию в 5 секунд. - Первый ход: после поступления сообщения первый ход ждёт до 2 секунд stdio-серверы, которые ещё подключаются. Пока сессия ждёт на этом этапе, первый ответ приходит медленнее. Чтобы изменить длительность этого ожидания, задайте
CLAUDE_CODE_MCP_STARTUP_WAIT_MSв окружении раннера. Эта переменная не меняет, какие серверы охватывает ожидание. Требуется Claude Code v2.1.274 или новее.
У claude mcp add нет флага alwaysLoad. Чтобы задать этот ключ, добавьте сервер командой claude mcp add-json, которая принимает его в JSON сервера и записывает в .claude.json. В вашем Dockerfile:
RUN claude mcp add-json core '{"type":"http","url":"https://mcp.example.com/mcp","alwaysLoad":true}' --scope user
Если инструменты сервера не появляются и в последующих ходах, проверьте, попал ли сервер в сессию вообще, как описано в разделе MCP-серверы.
Отключение встроенных инструментов сессии
Плоскость управления Anthropic подключает к облачным сессиям собственный MCP-сервер с именем Claude Code Remote. Claude использует инструменты этого сервера, чтобы создавать облачные запланированные задачи, запускать другие облачные сессии и управлять ими, подключать дополнительные репозитории и отслеживать активность в pull request.
Чтобы отключить весь сервер, добавьте в настройки правило запрета на уровне сервера. Плоскость управления регистрирует сервер под одним из трёх имён в зависимости от того, как была создана сессия. Claude Code сопоставляет имя в правиле точно, с учётом регистра, поэтому запишите правило для каждого имени, как показано ниже:
{
"permissions": {
"deny": [
"mcp__Claude_Code_Remote",
"mcp__claude-code-remote",
"mcp__bf7c680d-5fdc-5ef4-b4a0-abadb619bf0a"
]
}
}
Правило, указывающее весь сервер, распространяется и на инструменты, которые появятся у сервера позже. Чтобы отключить один инструмент и оставить остальные, добавьте к каждому правилу ещё два подчёркивания и имя инструмента, например mcp__Claude_Code_Remote__add_repo. Чтобы вообще запретить подключение сервера, а не удалять его инструменты, вместо этого добавьте три имени без префикса mcp__ как записи serverName в deniedMcpServers.
Поместите правила в настройки, управляемые сервером, чтобы они применялись к сессиям без изменений на раннере, или в ~/.claude/settings.json на раннере. На раннере, который отправляет запросы к модели в Bedrock или Agent Platform, используйте этот файл, поскольку настройки, управляемые сервером, не доходят до таких сессий. В разделе Разрешения и подтверждение инструментов объясняется, как настройки на раннере попадают в сессии.
Чтобы убедиться, что правила вступили в силу, запустите сессию в окружении и попросите Claude перечислить его инструменты MCP. Claude Code удаляет запрещённый инструмент из контекста Claude, поэтому запрещённые инструменты будут отсутствовать в его ответе.
Подсказка сеансов для отправки их работы
Размещенные Anthropic сеансы запускают хук Stop, хук Claude Code, который запускается, когда Claude заканчивает отвечать, который подсказывает Claude зафиксировать и отправить его работу. Средство выполнения не устанавливает один. Без него сеанс, который заканчивается с незафиксированными изменениями, оставляет эту работу только на диске средства выполнения, и кнопка Create PR в claude.ai/code остается неактивной до тех пор, пока ветка не существует на удаленном.
Эталонная реализация ниже имеет две части. Объедините блок настроек в ~/.claude/settings.json на хосте средства выполнения, который средство выполнения заполняет в каждый сеанс, и сохраните скрипт как ~/.claude/hooks/stop-hook-nudge.sh на хосте средства выполнения и сделайте его исполняемым:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"timeout": 10,
"command": "\"$CLAUDE_CONFIG_DIR/hooks/stop-hook-nudge.sh\""
}
]
}
]
}
}
#!/bin/sh
# Stop-hook reference implementation for self-hosted runners.
#
# Nudges Claude once per turn if the project directory has uncommitted
# changes OR unpushed commits, so work isn't lost when an idle session
# is released and so the "Create PR" button on claude.ai/code lights up.
#
# Runner-level (no repo changes): drop this file at ~/.claude/hooks/ on
# the runner host and merge the accompanying Stop-hook settings block
# into ~/.claude/settings.json — the runner seeds both into every session.
# Repo-level alternative: commit to <repo>/.claude/hooks/ and change the
# settings.json command path to $CLAUDE_PROJECT_DIR/.claude/hooks/.
#
# stdin: hook JSON payload (see https://code.claude.com/docs/en/hooks)
# stdout: {"decision":"block","reason":"..."} to nudge, or nothing to allow stop.
# Re-entry guard: the harness sets stop_hook_active=true when re-invoking
# the Stop hook after a block. Bail so we only nudge once per turn. The
# harness emits compact JSON (no space after the colon), which this
# pattern relies on; use jq if you need a whitespace-tolerant check.
in=$(cat)
case "$in" in *'"stop_hook_active":true'*) exit 0 ;; esac
d="$CLAUDE_PROJECT_DIR"
# Hardening: this script runs outside Claude Code's Bash sandbox, so its git
# calls skip some features that can start a program named in the checkout's
# git settings. They don't skip them all, so don't count on the sandbox alone.
GIT_OPTIONAL_LOCKS=0
export GIT_OPTIONAL_LOCKS
git() { command git -c core.fsmonitor= -c core.hooksPath=/dev/null "$@"; }
# Not a git repo → nothing to nudge.
git -C "$d" rev-parse --git-dir >/dev/null 2>&1 || exit 0
# No remote → "push to the remote" is unsatisfiable; bail.
[ -z "$(git -C "$d" remote 2>/dev/null)" ] && exit 0
# Uncommitted changes (staged, unstaged, or untracked). Exclude .claude/
# entirely — operator-seeded settings and CLI-written runtime state
# (scheduler lock, worktrees, routine state) live there and neither is
# "uncommitted work" the model needs to push.
s=$(git -C "$d" status --porcelain --ignore-submodules=dirty -- . ':(exclude).claude/' 2>/dev/null)
if [ -n "$s" ]; then
printf '{"decision":"block","reason":"There are uncommitted changes in the repository. Please commit and push these changes to the remote branch."}'
exit 0
fi
# Unpushed commits. Count commits on HEAD not reachable from any
# remote-tracking ref or FETCH_HEAD. This works uniformly for:
# - init+fetch checkouts (runner default: only FETCH_HEAD exists)
# - clone-based checkouts (origin/* exist)
# - the runner default: the child starts on the session's outcome
# branch, which the runner creates after checkout
# - detached HEAD, when a custom setup skips that branch creation
# With no reference point at all (never fetched), stay silent rather
# than false-positive on a read-only turn.
base=""
git -C "$d" rev-parse --verify -q FETCH_HEAD >/dev/null && base="FETCH_HEAD"
if [ -z "$base" ] && [ -z "$(git -C "$d" for-each-ref --count=1 refs/remotes/origin 2>/dev/null)" ]; then
exit 0
fi
# shellcheck disable=SC2086 # $base is either "" or "FETCH_HEAD", intentional word-split
unpushed=$(git -C "$d" rev-list HEAD --not $base --remotes=origin --count 2>/dev/null) || unpushed=0
if [ "$unpushed" -gt 0 ]; then
branch=$(git -C "$d" symbolic-ref --short -q HEAD)
if [ -n "$branch" ]; then
# $branch is attacker-influenced — git-check-ref-format(1) allows `"`
# in ref names. `\` is forbidden (rule 10) but escaped anyway as cheap
# defense-in-depth.
# Escape JSON metacharacters before interpolating into the hand-built
# payload so a branch like x","continue":false can't inject keys into
# the hook-output JSON the harness parses. $unpushed is safe — the
# -gt guard above rejects anything that isn't a plain integer.
branch_esc=$(printf '%s' "$branch" | sed 's/\\/\\\\/g; s/"/\\"/g')
printf '{"decision":"block","reason":"There are %s unpushed commit(s) on branch '\''%s'\''. Please push these changes to the remote repository."}' "$unpushed" "$branch_esc"
else
printf '{"decision":"block","reason":"There are %s unpushed commit(s) on a detached HEAD. Please create a branch and push it to the remote repository."}' "$unpushed"
fi
exit 0
fi
exit 0
Хук предлагает Claude сделать коммит и отправить изменения до завершения сессии и ничего не выводит, если каталог не является репозиторием git или у него нет удаленного репозитория. О сессиях с несколькими репозиториями см. раздел на что указывает $CLAUDE_PROJECT_DIR.
Хук Stop только побуждает Claude к действию. Чтобы коммит и отправка сработали, настройте:
- Идентификацию git: задайте
user.nameиuser.emailв образе средства выполнения или запустите средство выполнения с флагом--configure-git - Учётные данные для отправки: используйте git под управлением Anthropic или предоставьте учётные данные, как описано в разделе Включение конфигурации git в образ
- Для возобновлённых сессий: задайте флаг
--push-outcome-on-releaseна каждом средстве выполнения в окружении. Тогда возобновлённая сессия начинается с коммитов, которые Claude отправил в ветку сессии. Об ограничениях флага см. Возобновлённые сессии теряют неотправленную работу.
Хук Stop срабатывает не более одного раза за ход. Если коммит или отправка завершаются ошибкой, ход всё равно заканчивается. Найдите ошибку git в сессии и исправьте то, на что она указывает.
Для большого checkout увеличьте timeout в настройках хука Stop и перезапустите средства выполнения. Команда git status в скрипте пропускает core.fsmonitor, поэтому может выполняться дольше заданных там 10 секунд. Хук, превысивший таймаут, завершает ход без промпта.
Подмодули и хук Stop
Хук Stop проверяет, на каком коммите находится каждый подмодуль, но не файлы внутри него:
- Новый коммит в подмодуле: Claude получает промпт, пока родительский репозиторий не зафиксирует его, даже если
.gitmodulesзадаётignore = allдля этого подмодуля - Незафиксированные файлы в подмодуле: Claude не получает промпт, поэтому эта работа остаётся только на диске средства выполнения
- Неотправленные коммиты в подмодуле: хук Stop не проверяет, отправлены ли они, поэтому родительский репозиторий может указывать на коммит, который больше никто не может получить
Замена более ранней копии скрипта хука Stop
Найдите GIT_OPTIONAL_LOCKS в вашей копии stop-hook-nudge.sh. Если этой строки нет, у вас более ранняя копия. Замените её текущим скриптом везде, где вы его установили, затем перезапустите средства выполнения. Сохраните настройки хука Stop, за исключением увеличения timeout для большого checkout.
- Когда: если ваши сессии работают с включённой песочницей Bash, замените скрипт сейчас. В противном случае замените его при следующей сборке образа или перезапуске средства выполнения.
- Что меняется: незафиксированные файлы в подмодуле больше не вызывают промпт для Claude. Новый коммит в подмодуле, который
.gitmodulesпомечает какignore = all, теперь вызывает.
[!WARNING]
Хуки выполняются вне песочницы Bash, поэтому вызовы git в текущем скрипте пропускают некоторые функции git, способные запустить программу, указанную в настройках git в checkout. Но пропускают не все. Не полагайтесь только на песочницу и изолируйте сессии, как описано в разделе Усиление защиты развёртывания.
Разрешения и подтверждение инструментов
К self-hosted сессии не подключён терминал, поэтому запрос разрешения, оставшийся без ответа, приостанавливает ход, пока пользователь не ответит в интерфейсе. Управляющий уровень Anthropic передаёт список инструментов и правила разрешений каждой сессии вместе с данными задания; конфигурация по умолчанию заранее разрешает рутинные вызовы инструментов, включая Bash, а облачные сессии заранее разрешают редактирование файлов независимо от режима. Для вызова, который ничто заранее не разрешает, запрос выводится в интерфейсе сессии.
[!NOTE]
Закрепляйте авторежим только в тех окружениях, где контейнеры сессий работают с запретом исходящего сетевого трафика по умолчанию и применены остальные меры из раздела об усилении защиты. Рутинные вызовы инструментов, включая сетевые запросы
Bash, выполняются без участия человека как с набором инструментов, разрешённых по умолчанию, так и в авторежиме, поэтому именно сетевая граница ограничивает, куда могут обращаться эти вызовы.
Чтобы свести запросы к минимуму независимо от того, что передаёт управляющий уровень, закрепите авторежим в своём скрипте-обёртке или хуке command. Авторежим позволяет сессиям работать без рутинных запросов разрешений: отдельная модель-классификатор проверяет действия перед выполнением и блокирует отклонённые, а явные правила ask по-прежнему принудительно вызывают запрос; о том, что проверяет классификатор, рассказано на странице о режимах разрешений. Раннер добавляет вычисленные сервером флаги перед вызовом обёртки, а для флагов с одним значением, таких как --permission-mode, парсер учитывает последнее вхождение, поэтому флаг, добавленный после "$@", переопределяет значение, переданное сервером:
#!/bin/bash
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --permission-mode auto
Чтобы вместо этого заранее разрешить конкретные инструменты, добавьте --allowed-tools со своими правилами, например --allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*". Списочные флаги, такие как --allowed-tools и --disallowed-tools, накапливаются между вхождениями, а не переопределяют друг друга, поэтому ваши правила применяются поверх любых правил, переданных управляющим уровнем. Чтобы сузить набор, добавьте --disallowed-tools: этот флаг запрещает инструменты, даже если другое правило их разрешает.
Как собирается конфигурация каждой сессии
Раннер выдаёт каждой сессии собственный каталог конфигурации, заполняемый из снимка ~/.claude/ хоста, который раннер делает один раз при запуске: settings.json, CLAUDE.md, хуки, агенты, команды и скиллы в образе раннера применяются к каждой сессии как базовый пользовательский уровень. Если вы измените конфигурацию на работающем хосте, изменение вступит в силу только после перезапуска раннера.
Задайте SELF_HOSTED_RUNNER_HOST_CONFIG_DIR, чтобы заполнять каталог из другого пути, или укажите пустой каталог, чтобы отключить заполнение.
Сессии также читают следующие файлы настроек:
- Настройки проекта: вне треда проекта закоммиченный в репозиторий
.claude/settings.jsonнакладывается поверх базового пользовательского уровня. В сессии с несколькими репозиториями действует файл не более чем одного репозитория. - Управляемые настройки: сессии читают
managed-settings.jsonиз стандартного системного пути в образе раннера. О том, применяются ли его ключи наряду с настройками, управляемыми сервером, см. как Claude Code объединяет управляемые источники.
О порядке применения этих источников см. приоритет настроек.
Когда управляющий уровень Anthropic передаёт сессии хуки Claude Code, раннер устанавливает их рядом с вашей собственной конфигурацией, а не поверх неё. Требуется Claude Code v2.1.229 или новее; более старые раннеры игнорируют переданные хуки. Переданные хуки запускают python3, поэтому установите python3 в образ раннера.
- Куда они попадают: раннер записывает каждый переданный скрипт хука в зарезервированный подкаталог
hooks/.ccr-launcher/каталога конфигурации сессии и регистрирует скрипты в отдельном файле настроек, который передаёт сессии через--settings, не затрагивая заполненныйsettings.jsonи ваши собственные скрипты вhooks/<name>. Раннер пересоздаёт зарезервированный подкаталог для каждой сессии и не переносит в сессии содержимое хоста из~/.claude/hooks/.ccr-launcher/. - Кто их создаёт: управляющий уровень заполняет скрипты из фиксированных констант собственного развёртывания, никогда — из данных конкретной сессии или сторонних данных.
- Что по-прежнему ими управляет: хуки, переданные через
--settings, входят в обычную объединённую конфигурацию хуков, а не в управляемый уровень, поэтому ваши управляемые настройки продолжают действовать.disableAllHooksотключает их, и они не входят в категории, которыеallowManagedHooksOnlyоставляет загруженными.
Когда человек запускает собственную сессию, Claude Code загружает скиллы, включённые для его аккаунта claude.ai, в каталог конфигурации этой сессии. Запуск облачной запланированной задачи не получает скиллы её владельца, а сессия, которая отправляет запросы к модели в Bedrock или Agent Platform, не загружает никаких скиллов. Если такой сессии нужен скилл, добавьте его в образ раннера или, вне треда проекта, сделайте коммит в .claude/skills/ репозитория.
За пределами сессий Claude Tag сессия в self-hosted окружении по умолчанию работает с отключённой автоматической памятью. Для инструкций, которые должны сохраняться между сессиями, используйте CLAUDE.md в образе раннера или в репозитории.
Снимок ~/.claude/ хоста, который делает раннер, не включает каталог projects/. Место хранения автоматической памяти по умолчанию находится в этом каталоге. Если вы положите туда файлы памяти, раннер не перенесёт их в сессии, и они не включат автоматическую память.
Остановка сессий, которым не удалось получить настройки, управляемые сервером
Чтобы сессия, получающая настройки, управляемые сервером, не запускалась, если получить их не удалось, задайте forceRemoteSettingsRefresh значение true в managed-settings.json в образе раннера. Ответ API Anthropic о том, что у вашей организации нет настроек, управляемых сервером, считается успешным получением.
Прежде чем добавлять ключ, проверьте, запускает ли окружение сессии каналов Claude Tag и отправляет ли оно запросы к модели в Bedrock или Agent Platform:
- Сессии каналов Claude Tag: не задавайте ключ на раннерах в окружении, на которое Owner направляет каналы Claude Tag. API Anthropic отклоняет запрос этих сессий на получение настроек, управляемых сервером, поэтому при заданном ключе они не запускаются. Поместите их правила в
managed-settings.jsonобраза раннера, который они по-прежнему читают. Чтобы использовать ключ для других сессий, запускайте их в отдельном окружении. - Bedrock или Agent Platform: сессии у этих провайдеров не получают настройки, управляемые сервером, поэтому они запускаются независимо от того, задан ли ключ. Вместо этого применяйте свои правила через тот же
managed-settings.json.
Этот managed-settings.json задаёт ключ:
{
"forceRemoteSettingsRefresh": true
}
Задайте ключ в этом файле, даже если вы также задали его в консоли администратора claude.ai. У новой сессии нет кэшированной копии ваших настроек, управляемых сервером, поэтому если ключ задан только в консоли, у сессии его ещё нет. Если затем получение настроек завершится неудачей, сессия будет работать без этих настроек, пока следующая попытка не окажется успешной.
Чтобы убедиться, что сессия не запустилась из-за неудачного получения настроек, посмотрите последний вывод Claude Code в сессии или лог раннера. В нём будет Your organization requires remote managed settings to load, but they could not be loaded.
Настройки репозиториев в сессиях с несколькими репозиториями
В треде проекта .claude/settings.json ни одного репозитория не действует как настройки проекта, а .mcp.json и скиллы ни одного репозитория не загружаются, где бы ни начинался тред. Чтобы дать таким тредам скилл, добавьте его в образ раннера.
В любой другой сессии с несколькими репозиториями Claude Code читает настройки проекта из каталога, в котором начинается сессия, поэтому как настройки проекта действует .claude/settings.json не более чем одного репозитория. Хук, определённый в файле другого репозитория, не выполняется, правило запрета в нём не применяется, а его env не задаётся.
--capacity 1(по умолчанию) со встроенным checkout: сессия начинается в первом репозитории из своего списка репозиториев..claude/settings.jsonэтого репозитория действует как настройки проекта, и его.mcp.jsonзагружается, а у остальных репозиториев — нет.--capacityбольше единицы или хукcheckout: сессия начинается в отдельном каталоге сессии, содержащем checkout-копии..claude/settings.jsonни одного репозитория не действует как настройки проекта,.mcp.jsonни одного репозитория не загружается, а$CLAUDE_PROJECT_DIRв команде хука указывает на этот каталог, а не на checkout-копию.
CLAUDE.md каждого репозитория загружается, где бы ни начиналась сессия. Вне треда проекта загружаются и его скиллы. Раннер передаёт каждый репозиторий в Claude Code как дополнительный каталог, поэтому Claude Code также читает ключи enabledPlugins и extraKnownMarketplaces из .claude/settings.json каждого репозитория.
Чтобы запускать хук или применять правило разрешений в каждой сессии, поместите его в ~/.claude/settings.json на хосте раннера. Раннер переносит файл хоста в каждую сессию, где бы она ни начиналась. Указывайте путь в правиле Read или Edit как абсолютный шаблон с // или относительный от домашнего каталога с ~/, потому что другие шаблоны привязываются к источнику настроек или текущему каталогу.
Правила разрешений, закоммиченные в репозиторий
Не добавляйте голую запись "Edit", "Write" или "NotebookEdit" в закоммиченный в репозиторий permissions.allow. Голое правило для файлового инструмента соответствует инструменту независимо от пути и разрешает запись в любом месте хоста, а не только в рабочем пространстве, поэтому защита раннера, ограничивающая область записи, помечает сессию; с --confine-repo-settings enforce раннер отказывается запускать сессию, вместо того чтобы записать предупреждение в лог и продолжить. См. раздел об усилении защиты.
Репозиторию вообще не нужно правило для файловых инструментов: облачные сессии заранее разрешают редактирование файлов независимо от режима. Если вы всё же делаете коммит правила, ограничьте его рабочим пространством, например "Edit(/**)"; одиночная косая черта в начале указывает путь относительно корня проекта, которым является рабочее пространство сессии. Голые правила для файловых инструментов допустимы в settings.json уровня хоста, который ведёт оператор, поскольку этот файл не коммитится в репозиторий.
defaultMode со значением auto учитывается только из файла настроек уровня образа или пользовательского уровня, поэтому извлечённый репозиторий не может сам предоставить себе авторежим. О том, какие режимы принимают облачные сессии, и о полном синтаксисе правил см. режимы разрешений.
Что дальше
- Reference: каждый флаг CLI, переменная окружения и метрика
- Verify session identity: проверьте токен сеанса из служб вне средства выполнения