Перейти к содержанию

Исполнение и runner

Отказы автономного исполнителя (control-plane-agent) и прогонов задач: исполнитель не берёт работу, прогон падает, ветка не публикуется, машина упирается в память. Статья для инженера, сопровождающего runner-хост, и оператора, который разбирает упавшие runs.

Как быстро понять, что происходит

# контейнерный вариант
docker compose -f deploy/runner/docker/compose.yml ps
docker compose -f deploy/runner/docker/compose.yml logs --since 30m runner

# systemd-вариант
systemctl status <юнит>
journalctl -u <юнит> -n 100      # или файл журнала из StandardOutput юнита

Ищите в логе:

  • ошибки credential (iam_*, has no credentials) — исполнитель не может даже открыть сессию;
  • failure_reason при fail_run — почему закрыт конкретный run;
  • no task_types.read, workspace busy, lease lost, no changes.

Упавший run виден и в Control Plane: failure_reason в карточке run, трасса прогона — артефакт transcript и actions tool.<имя> (см. Трасса прогонов).

Причины завершения run (failure_reason)

failure_reason Что случилось Что делать
restart_recovery Демон перезапустился посреди прогона (выкладка, OOM, рестарт машины). При старте он нашёл свой осиротевший run и закрыл его; задача вернулась в очередь Ничего, если единичный случай. Если повторяется на одной задаче — смотреть OOM (ниже)
workspace_busy Рабочая копия задачи занята другим процессом (замок в каталоге рабочих копий) Проверить, не запущены ли два демона с одним CONTROL_PLANE_AGENT_WORKTREE_ROOT
lease_lost Аренда claim истекла во время работы адаптера (heartbeat не прошёл); демон прекратил запись, чтобы не получить отказ fencing Проверить сеть до платформы, доступность API; при частых случаях — длинные паузы процесса (swap, CPU-квота)
ownership_lost Claim перехвачен или освобождён, пока демон работал Разобраться, кто ещё взял задачу; убедиться, что у исполнителей разные principals

Ошибки вызова скиллов

Если задача исполняется скиллом (HTTP-вызов по контракту), отказ приходит с кодом скилла:

Код Причина Решение
insecure_endpoint Контракт скилла требует токен, а endpoint не https:// Исправить endpoint скилла; токены отправляются только по https
audience_not_allowed Исполнитель не выдаёт токены для audience скилла Добавить audience в CONTROL_PLANE_SKILLS_ALLOWED_AUDIENCES исполнителя
executor_auth_unavailable Исполнитель не смог получить токен для audience (нет IAM identity или обмен отказал). Повторяемая ошибка: скилл может выполнить другой исполнитель Проверить PAT исполнителя и наличие audience в его потолке

Исполнитель не берёт задачи

Симптом Причина Решение
В логе ошибки iam_* или has no credentials Нет PAT, неверные права файла, несколько credentials без IAM_PRINCIPAL См. Аутентификация и доступ
Задача в очереди, но исполнитель её не видит CONTROL_PLANE_AGENT_ONLY_ASSIGNED=1, а задача не назначена на его principal Назначить задачу на CP principal исполнителя (assigneeId)
Не видит задачи нужного workspace CONTROL_PLANE_AGENT_WORKSPACE указывает на другой workspace Исправить переменную. Не путать с CONTROL_PLANE_AGENT_WORKTREE_ROOT — это каталог рабочих копий
В логе no task_types.read: leaving … alone У binding исполнителя нет права task_types.read. Без него демон не может понять, кодовая это задача или скилл, и fail-closed пропускает типизированную работу Добавить task_types.read в права binding (bootstrap включает его в права агентов по умолчанию)
Задача взята, но ничего не происходит Идёт длинный ход кодового агента; ход ограничен CONTROL_PLANE_CLAUDE_TIMEOUT (по умолчанию 3600 с) Проверить, жив ли процесс агента; трасса run показывает вызовы инструментов вживую

Исполнитель берёт чужие задачи

Без CONTROL_PLANE_AGENT_ONLY_ASSIGNED=1 и CONTROL_PLANE_AGENT_WORKSPACE демон берёт первую доступную задачу по приоритету из всех, что ему видны, — включая эпики и задачи других репозиториев. Для проверок заведите отдельный workspace-песочницу.

Кодовый агент

Симптом Причина Решение
В отчёте агента: каждый вызов инструмента требует подтверждения, работа идёт вслепую Режим разрешений по умолчанию (acceptEdits) не разрешает команды без человека CONTROL_PLANE_CLAUDE_PERMISSION_MODE=bypassPermissions — только внутри изолированного периметра (контейнер без секретов платформы или непривилегированный пользователь с ProtectSystem=strict)
Claude Code не авторизован на runner-хосте На сервере нет браузера для входа Выпустить токен claude setup-token на машине с браузером и передать в CLAUDE_CODE_OAUTH_TOKEN (файл-секрет или env-файл 0600)
Агент стабильно проваливает задачи после отзыва подписки Токен подписки отозван, демон продолжает брать задачи Остановить исполнителя до замены токена
Агент пишет код против выдуманного API соседнего сервиса, тесты зелёные на собственных моках Агент не видит кода соседа, контракт восстанавливает по догадке Добавить соседний репозиторий в CONTROL_PLANE_AGENT_NEIGHBOURS (читать код, а не угадывать); в постановке задачи явно указывать источник схем и требовать contract-тестов
Тесты, которым нужен Docker, не запускаются в контейнерном исполнителе Docker-сокет в контейнер намеренно не пробрасывается Интеграционные тесты — в CI; для unit-тестов с БД исполнителю даны тестовые базы (db-test, memory-db-test)

Рабочие копии

Симптом Причина Решение
Сборка рабочей копии падает: не найден ../platform-auth-sdk Не заданы соседи: path-зависимость ждёт SDK соседней папкой Задать CONTROL_PLANE_AGENT_NEIGHBOURS=platform-auth-sdk=<runner-root>/platform-auth-sdk.git и CONTROL_PLANE_AGENT_SUPERPROJECT
git worktree add падает с invalid reference Суперпроект закрепил ревизию соседа, которой ещё нет в его зеркале Демон сам делает fetch зеркала соседа перед созданием копии (best-effort). Если forge был недоступен — git -C <зеркало> fetch origin '+refs/heads/*:refs/heads/*' от пользователя исполнителя
Агент чинит код, которого в main уже нет Bare-зеркало репозитория задач давно не обновлялось Обновить зеркало (entrypoint контейнера делает это при старте; в systemd-варианте — вручную или по таймеру)
Каталог рабочих копий растёт Копии хранятся для повторных попыток CONTROL_PLANE_AGENT_MAX_WORKSPACES (по умолчанию 8)

Публикация веток

Ветку task/<publicId> публикует демон, а не агент: claim и fencing token держит он. Push никогда не форсируется и никогда не трогает базовую ветку; неудача публикации не валит run.

Симптом Причина Решение
Run успешен, в артефакте commit поле published: false Push не удался; причина — только в логе исполнителя Смотреть лог исполнителя вокруг завершения run
В логе: could not read Username for 'https://…' Процессу не задан HOME, git не нашёл ~/.gitconfig с credential helper. systemd не выставляет HOME даже сервисам от root. Выглядит как проблема прав в forge, но до forge запрос не дошёл Environment=HOME=/home/<пользователь> в юните (drop-in); в контейнере HOME задан образом
Push отклонён forge (403, denied) Токен forge без права записи в репозиторий задач или отозван Выпустить токен с Contents: write на репозиторий задач
Push отклонён как non-fast-forward Ветка в forge разошлась с локальной (её правил человек) Разобрать вручную: демон намеренно не перезаписывает историю
В логе no changes in …; nothing to commit, ветки нет Агент не изменил файлы рабочей копии Проверить постановку задачи и трассу run. Если агент закоммитил сам, демон всё равно публикует ветку
Задача на код сразу done, ревью не запрошено Нет опубликованного коммита: критерии review и merge пропущены (skipped) Починить публикацию ветки; проверить артефакт commit (published)
Прогон failed: executor_blocked, задача в blocked Исполнитель сообщил, что не может сделать работу (checkpoint blocked) Причина — в комментарии задачи; устранить и вернуть задачу в работу (см. Агент остановился без результата)

Установка и обновление (systemd-вариант)

Симптом Причина Решение
После uv tool install --reinstall исполнитель работает на старом коде Установка выполнена от root и ушла в /root/.local/share/uv/tools, мимо каталога, из которого запускается сервис Устанавливать от пользователя исполнителя с его UV_TOOL_DIR/UV_TOOL_BIN_DIR, см. Обновление и миграции
uv tool install от пользователя исполнителя падает с Permission denied В каталогах исходников или инструментов появились файлы root (после git pull или запуска python от root) chown -R <пользователь>:<группа> <runner-root>/src <runner-root>/tools; дальше все операции — от пользователя исполнителя
Юнит не находит uv или другие утилиты PATH юнита не содержит ~/.local/bin пользователя Добавить каталог в Environment=PATH=… через drop-in
Демон не видит переменные со значением из нескольких слов при ручном запуске При source env-файла в shell значение без кавычек обрезается Кавычки вокруг значений с пробелами

Память и CPU

Симптом Причина Решение
Runs закрываются restart_recovery, в dmesg / journalctl -k — Out of memory: Killed process Процесс агента упёрся в MemoryMax (systemd) или mem_limit (контейнер) Если повторяется на одной задаче — она слишком тяжела для машины: выполнять на машине крупнее
OOM случается, когда работают два исполнителя одновременно Сумма потолков памяти исполнителей больше физической памяти Привести сумму MemoryMax/mem_limit к объёму RAM за вычетом ОС; см. Ресурсы и масштабирование
Соседние сервисы на машине тормозят во время прогонов Нет ограничения CPU/IO CPUQuota, IOWeight в юните; cpus в compose

Экстренные действия

# Остановить исполнителя (безопасно в любой момент)
docker compose -f deploy/runner/docker/compose.yml stop runner
systemctl stop <юнит>

# Отобрать доступ: отозвать binding и PAT исполнителя
#   POST /api/v1/iam-bindings/<binding-id>:revoke
#   POST /api/v1/tenants/<t>/platform-access-tokens/<id>:revoke

После отзыва PAT исполнитель не получает новых access token; уже выданный живёт до 300 с — отзыв binding закрывает вход в Control Plane сразу. Локальный principal и история работы сохраняются. Потеря runner-хоста — см. Аварийные процедуры.

См. также