Исполнение и 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-хоста — см. Аварийные процедуры.