Для AI-агентов: markdown этой страницы — /docs-content/infra/deploy.md индекс документации — /llms.txt

Deploy API

Если GET /v1/me содержит deployment.standalone.reuseTarget, используйте его serverId: сначала прочитайте сервер, затем отправьте код на этот же id. Создание новой VM не требуется. Без такого сигнала сначала проверьте capabilities.servers.create.available. При TRIAL_PORTAL_LIMIT не перебирайте имена, ключи, провайдеры и планы: следуйте userMessage и alternatives. Успешный create-reuse возвращает HTTP 201 с reused: true; APPLICATION_REUSE_CONFLICT (HTTP 409, retryable: true) означает конкурентное изменение привязки — повторите запрос.

Развёртывание приложений на BLACKHOLE-серверах без SSH. Все операции идут через агент туннеля: команды выполняются на сервере, файлы загружаются через туннель, логи читаются из системного журнала (утилита journalctl). AI-агенты должны использовать эту группу эндпоинтов для полного цикла «взять код из git → установить зависимости → запустить сервис → читать логи».

Требования: сервер в режиме BLACKHOLE, статус running, blackholeStatus: CONNECTED. В OPEN-режиме Deploy API вернёт NOT_BLACKHOLE.

Спящую отдельную виртуальную машину поднимать заранее не нужно: выкладка, команда, загрузка файла и чтение журнала будят сервер в статусе sleeping сами и ждут готовности в том же запросе — до 6.5 минут. Не уложился — приходит 503 WAKE_TIMEOUT, сервер возвращается в sleeping, повторный запрос безопасен. Разбор — Полный деплой приложения, раздел «Известные особенности».

Это контракт для Black Hole VM (kind: "STANDALONE"). Для galaxy-приложения (kind: "GALAXY_APP") требование CONNECTED не действует — деплой сам собирает контейнер. Полная модель и контракт деплоя galaxy-приложения — Galaxy-приложение.

OOM galaxy-приложения → выпуск на выделенный сервер. Если деплой galaxy-приложения падает с 502 GALAXY_APP_START_FAILED именно по причине OOM (приложение переросло лимит памяти контейнера 512 МБ), ответ содержит структурную подсказку error.hint с recoveryAction: "graduate-to-dedicated-vm". Рекомендованное действие — пересоздать приложение на отдельной виртуальной машине: POST /v1/infra/servers с placement: "dedicated" и graduateFrom (идентификатор упавшего galaxy-приложения, который будет удалён после создания сервера), затем POST /v1/infra/servers/:id/deploy с тем же исходным кодом. Защита от повторного создания через заголовок Idempotency-Key на этот выпуск не распространяется — вместе с graduateFrom он вернёт 400 IDEMPOTENCY_UNSUPPORTED_WITH_GRADUATION. Допустимо, когда политика serverCreation портала разрешает создание серверов и вы в пределах квоты, выделенная виртуальная машина тарифицируется (засыпает при простое). Подсказка добавляется только при причине OOM, а не при обычном краше.

Формат ответов: все эндпоинты по умолчанию отдают JSON (201/200 со {"success": true, ...}). Для /deploy и /exec доступен потоковый режим через ?stream=true — возвращается поток SSE (Server-Sent Events) с построчным прогрессом. Для AI-агентов и MCP-клиентов всегда используйте JSON (не добавляйте ?stream=true) — они не умеют разбирать SSE.

Ограничения частоты:

Ограничение Значение
Операций в минуту на сервер 10
Одновременных exec/deploy 1 на сервер
Таймаут exec 1–600 секунд (по умолчанию 300)
Размер встроенного тела (base64 в /upload, /deploy source.content) 96 МБ тела, около 72 МБ архива. Сверх потолка — 413 INLINE_SOURCE_TOO_LARGE
Размер файла через URL (/upload url, /deploy source.url) 500 МБ
Размер архива сохранённой версии (/deploy source.versionId) 500 МБ
Размер multipart-архива в /deploy 500 МБ, пока архив уходит в хранилище потоком. Иначе около 72 МБ — условия

Скоуп: vibe:infra

Выполнить команду

POST /v1/infra/servers/:id/exec

Выполняет shell-команду на BLACKHOLE-сервере через агент туннеля. Полный стандартный вывод (stdout), поток ошибок (stderr) и код возврата передаются клиенту. По умолчанию ответ — JSON после завершения команды (рекомендуется для AI-агентов и скриптов). На STANDALONE для построчного прогресса передайте ?stream=true и читайте SSE-события stdout/stderr/exit. Отказ при выполнении команды приходит в потоковом режиме событием error.

Для AI-агентов и MCP-клиентов обязательно используйте JSON-режим (без ?stream=true) — SSE они разбирать не умеют. Официальный MCP-клиент платформы Вайбкод (инструмент manage_server_deploy) снимает этот параметр сам и всегда работает в JSON-режиме.

Потоковый режим даёт живой прогресс, но не увеличивает предельное время выполнения: синхронный предел одинаков для JSON и потокового режима и равен timeout + 30 секунд, а сам timeout ограничен 600 секундами. На STANDALONE для более долгой работы с сохранением результата используйте async=true — см. «Durable exec — длительная команда» ниже. Другой вариант — самостоятельно управлять фоновой задачей.

На каких серверах работает

Тип сервера приходит в поле kind ответов GET /v1/infra/servers и GET /v1/infra/servers/:id.

kind Команда выполняется Что учесть
STANDALONE — отдельная виртуальная машина да Контракт страницы действует целиком, включая workdir и env
GALAXY_APP — приложение в галактике да, внутри контейнера приложения Поля workdir и env не поддерживаются — 400 GALAXY_EXEC_NO_WORKDIR_ENV. Используйте cd /opt/app; FOO=bar node script.js только для несекретных значений. Секреты загружайте внутри контейнера из существующего защищённого файла или настроенного хранилища секретов приложения; не вставляйте их значения в текст команды. Ошибки во время выполнения приходят со статусом 502, а не 200. Исключений два: EXEC_BUSY приходит как 409, а EXEC_NO_EXIT — как 200 с success: false, потому что это не отказ канала, а отсутствие вердикта у отработавшего вызова (см. ниже) Поддерживается только синхронный exec с таймаутом до 600 секунд. async=true получает 400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND без исполнения или перехода.
GALAXY — машина, несущая контейнеры приложений нет — 403 GALAXY_HOST_EXEC_FORBIDDEN На машине стоят контейнеры разных ключей одного аккаунта Битрикс24, поэтому целью команд она не является. Выполняйте команду на приложении, по его собственному id (kind равен GALAXY_APP). Свободное место на машине смотрите в кабинете, на карточке галактики — см. Galaxy-приложение Длительные запросы получают 400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND до отправки команды.

Параметры

Параметр В Тип Обяз. По умолч. Описание
id path string (UUID) да — ID BLACKHOLE-сервера, status: running, blackholeStatus: CONNECTED
stream query string нет — true — синхронный SSE-режим на STANDALONE. Для JSON не передавайте. Exec Galaxy возвращает блокирующий JSON без живого SSE-потока
async query string нет false true — длительное исполнение отдельной машины с сохранённым результатом и ответом 202. Другие виды: 400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND, без синхронного перехода. Несовместим с stream=true

Поля запроса (body)

Поле Тип Обяз. Описание
command string да Shell-команда. 1–10 000 символов
timeout number нет Таймаут исполнения в секундах: 1–600 синхронно; 1–86400 с async=true только на STANDALONE. По умолчанию 300
workdir string нет Только отдельная машина. До 500 символов. Существующая директория, чей очищенный абсолютный путь — /opt, /srv, /tmp, /root, /var/log, /var/lib, /etc/systemd, /etc/nginx или вложенный в один из них. Относительные пути, оставшиеся .. в очищенном пути и чувствительные префиксы ниже запрещены. Без поля используется рабочая директория процесса исполнения
env object нет Переменные окружения: { "KEY": "value" }. Только строковые значения

Проверка по OpenAPI. Стандартный requestBody допускает timeout до 86400 секунд, чтобы обычные валидаторы и сгенерированные клиенты принимали поддерживаемые запросы async=true на STANDALONE. Схема тела не учитывает параметр async: синхронный API по-прежнему отклоняет timeout больше 600 секунд, даже если стандартная схема пропускает тело. Контракты по режимам описаны в #/components/schemas/InfraSyncExecRequest (до 600 секунд) и #/components/schemas/InfraDurableExecRequest (до 86400 секунд). Расширение x-vibe-request-schema проверяет собранные {query,body} с учётом режима; вид сервера проверяется во время исполнения.

Расширение x-vibe-request-schema проверяет собранный объект {query, body}: предел таймаута по режиму и допустимое сочетание async/stream. Вид сервера проверяется при вызове API; выбор схемы длительного тела не отменяет требование STANDALONE.

Для workdir очистка пути предшествует проверке. Следующие префиксы запрещены даже внутри разрешённого корня: /etc/shadow, /etc/passwd, /etc/sudoers, /etc/cron, /etc/ssh/, /root/.ssh/, /root/.gnupg/, /root/.bashrc, /root/.profile, /root/.bash_history. Запрещённый путь даёт EXEC_WORKDIR_DENIED, недоступная директория — EXEC_WORKDIR_NOT_FOUND, путь к файлу — EXEC_WORKDIR_NOT_DIR. Длительный запрос может получить HTTP 502 за запрещённый путь до запуска; принятая задача возвращает последующие проверки директории через операцию. См. коды отказа длительного exec.

Примеры

curl — личный ключ

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/exec" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"command": "ls -la /opt/app", "timeout": 30}'

curl — OAuth-приложение

Terminal
curl -X POST "https://vibecode.bitrix24.tech/v1/infra/servers/SERVER_ID/exec" \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command": "npm ci --production", "workdir": "/opt/app", "timeout": 180}'

JavaScript — личный ключ

javascript
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/exec`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      command: 'node -v',
      timeout: 10,
    }),
  }
)
const { data } = await res.json()
console.log(`exit ${data.exitCode} за ${data.duration}ms:\n${data.stdout}`)

JavaScript — OAuth-приложение

javascript
// Команда с переменными окружения и рабочей директорией
const res = await fetch(
  `https://vibecode.bitrix24.tech/v1/infra/servers/${serverId}/exec`,
  {
    method: 'POST',
    headers: {
      'X-Api-Key': 'YOUR_APP_KEY',
      'Authorization': 'Bearer USER_SESSION_TOKEN',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      command: 'npx tsx scripts/seed.ts',
      workdir: '/opt/app',
      env: { DATABASE_URL: 'postgresql://localhost/mydb' },
      timeout: 60,
    }),
  }
)

Поля ответа

Поле Тип Режим Описание
success boolean Оба Синхронный 200: true, если команда выполнилась, включая ненулевой код. Длительный 202: запрос принят для сверки; это не доказательство запуска
data.exitCode number Синхронный 200 Код возврата процесса
data.stdout string Синхронный 200 Стандартный вывод
data.stderr string Синхронный 200 Поток ошибок
data.duration number Синхронный 200 Время выполнения в миллисекундах
data.truncated boolean Синхронный 200 Достигнут ли общий предел stdout+stderr 1 МиБ (1 048 576 байт)
data.operationId string Оба Идентификатор запуска; при отказе — error.operationId. Обязателен в длительном 202: без сохранённой записи отправки нет. Синхронная команда может выполняться без него, если запись не удалось создать
data.status string Длительный 202 running: подтверждение запуска может быть потеряно; читайте ту же операцию
data.statusUrl string Длительный 202 URL чтения операции и сохранённого результата

Длительный ответ 202 не содержит вывода команды или кода возврата. Сохранённые поля читайте через разрешённую ручку исхода операции.

Пример ответа

JSON
{
  "success": true,
  "data": {
    "exitCode": 0,
    "stdout": "Linux epd65hdv07p8g89c0c06 6.8.0-107-generic #107-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar 13 19:51:50 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux\n",
    "stderr": "",
    "duration": 5,
    "truncated": false,
    "operationId": "clz9k2m4x0001qw8h3f7d2n5q"
  }
}

Длительная команда отдельной машины принята (202):

JSON
{
  "success": true,
  "data": {
    "operationId": "clz9k2m4x0001qw8h3f7d2n5q",
    "status": "running",
    "statusUrl": "/v1/infra/operations/clz9k2m4x0001qw8h3f7d2n5q"
  }
}

Пример ответа при ошибке

409 EXEC_BUSY — приём команд занят. Отказ платформенного лока возвращает сигналы повтора из этого примера:

JSON
{
  "success": false,
  "error": {
    "code": "EXEC_BUSY",
    "message": "Another operation is running on this server",
    "retryable": true,
    "retryAfter": 10
  }
}

Если ответ содержит retryable: true и retryAfter, он также несёт заголовок Retry-After с тем же интервалом в секундах. Соблюдайте этот интервал; поле error.hint.autoExpiresInSeconds, если оно есть, задаёт полный верхний предел жизни лока. Агентский отказ длительному запуску возвращает error.operationId без retryable, retryAfter и Retry-After. Прочитайте эту операцию до нового запроса exec; неизвестный исход требует сверки состояния и независимого подтверждения остановки команды.

Ошибки

Ошибки эндпоинта делятся на две группы, и отдаются они по-разному. Отказ до запуска команды приходит с настоящим HTTP-статусом. Отказ во время выполнения приходит в теле ответа на отдельной виртуальной машине (kind: "STANDALONE"). У galaxy-приложения (kind: "GALAXY_APP") он приходит со статусом 502, и исключений там два: занятость exec-канала (EXEC_BUSY) приходит настоящим 409, потому что это отказ «занято», а не сбой, а EXEC_NO_EXIT — с 200 и success: false.

Ошибки до запуска команды

HTTP Код Описание
400 VALIDATION_ERROR Нарушена схема запроса (пустая команда, неверный таймаут, workdir длиннее 500 символов, неверное значение async или async=true вместе с stream=true).
400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND async=true направлен на сервер вида, отличного от STANDALONE, включая GALAXY_APP и GALAXY. Отказ до блокировки, создания операции и отправки команды; идентификатора операции и синхронного перехода нет. В Galaxy доступен отдельный синхронный запрос с таймаутом до 600 секунд
400 NOT_BLACKHOLE Сервер в режиме OPEN — Deploy API недоступен
400 COMMAND_TOO_LONG Команда длиннее 10 000 символов. В ответе hint: большие данные и скрипты передавайте через /upload, затем bash /путь/скрипт.sh
400 GALAXY_EXEC_NO_WORKDIR_ENV В команде для galaxy-приложения (kind: "GALAXY_APP") переданы workdir или env. Контейнерный запуск их не принимает — используйте cd и только несекретные присваивания вроде FOO=bar; секреты загружайте внутри контейнера из существующего защищённого файла или настроенного хранилища секретов приложения
401 MISSING_API_KEY Не передан заголовок X-Api-Key
401 INVALID_API_KEY Ключ не опознан: такой строки на платформе нет
402 ACCOUNT_FROZEN Баланс Вайбкод заморожен. Запрос отбивается до операции, спящий сервер при этом не будится — пополните баланс и повторите
403 WRONG_KEY Сервер существует, но вызывающему не принадлежит. Операции над содержимым приложения — выкладка, команда, загрузка файла и чтение логов — открыты по любому из трёх оснований: управляющий ключ сервера, ключ, приложение которого привязано к этому серверу (Application.serverId), либо членство в команде разработки этого сервера (обе роли, «Разработчик» и «Администратор»). Токены доступа и загрузка значка требуют именно управляющего ключа. Управление самой машиной открыто ещё и роли «Администратор» команды. В ответе — hint с восстановлением из двух шагов. Подробнее — Восстановление доступа к серверу
403 SERVER_WAKE_BLOCKED Сервер спит, а пробуждение запрещено — например, закончился доступ к платформе или действует административная блокировка. Команда такой сервер не поднимает, повтор не поможет, пока запрет не снят. Разбор запрета — Разбудить сервер
403 GALAXY_HOST_EXEC_FORBIDDEN Команда пришла на id машины-галактики (kind: "GALAXY"). Выполняйте её на приложении, по его собственному id — см. «На каких серверах работает» выше
403 INFRA_FORBIDDEN_FOR_COWORK_KEY Вызов сделан ключом Cowork/Code — такой ключ работает только с данными, изменяющие операции ему закрыты. Что делать — Проектный ключ для деплоя
403 EXTERNAL_COLLABORATOR_KEY_SERVER_MISMATCH Вызов сделан ключом внешнего участника разработки, а в адресе указан не тот сервер, для которого ключ выпущен. Такой ключ действует ровно на одном сервере, и ответ не сообщает, существует ли запрошенный сервер. Для работы с другим сервером нужен ключ того внешнего членства
403 EXTERNAL_COLLABORATOR_NOT_A_MEMBER Ключ внешнего участника обращается к своему серверу, но доступа сейчас нет: членство снято, истёк его срок либо для аккаунта Битрикс24, которому принадлежит сервер, отключены внешние участники разработки. Код эти причины не различает и сведений о сервере не раскрывает. Членство перечитывается на каждом запросе, поэтому снятый доступ перестаёт работать сразу
404 NOT_FOUND Сервера с таким id нет или он удалён. Сервер, который существует, но вам не принадлежит, отвечает 403 WRONG_KEY — код различает «нет такого сервера» и «нет прав на него»
409 EXEC_BUSY Приём занят другой командой или выкладкой. Отказ платформенного лока несёт Retry-After и retryable: true / retryAfter; соблюдайте этот интервал. Агентский отказ длительному запуску отдельной машины тоже приходит как 409 с error.operationId, но без этих полей повтора. Прочитайте операцию до повторного запроса. Работающая длительная задача отдельной машины штатно занимает канал после 202, без autoExpiresInSeconds; читайте её результат вместо /unstick или удаления лока. В Galaxy канал общий для всех приложений и выкладок хоста. При неизвестном исходе сверьте состояние и логи и обратитесь в поддержку; отсутствие error.hint.holder не доказывает простой. Восстановление лока описано в /unstick и DELETE /lock и требует независимо подтверждённого простоя. Владельцу galaxy-приложения принудительное восстановление недоступно
409 SERVER_NOT_READY Сервер не готов к операции: не запущен, туннель не подключён или успешный непустой снимок Gateway подтвердил отсутствие туннеля при сохранённом CONNECTED. Этот ответ выдаётся только когда repair не подтвердил восстановление; недоступный или пустой снимок не считается подтверждением отсутствия, и API использует сохранённый статус. Платформа пытается восстановить подтверждённо отсутствующий туннель до запуска команды; если это не удалось, ответ несёт hint. Спящий сервер этот код не описывает — его платформа будит сама. Восстановите туннель вызовом /repair и повторите запрос
409 WAKE_IN_PROGRESS Спящий сервер уже будит другой запрос. Дождитесь его завершения и повторите
422 VM_MISSING У записи сервера нет виртуальной машины у облачного провайдера, поэтому будить нечего. Сервер нужно заменить: удалить и создать новый. Адрес каждого шага error.hint называет, только если шаг доступен этому запросу, а для недоступного — причину отказа и что изменить. Поле error.userMessage приходит, только когда доступны оба шага
422 VM_POINTER_LOST У записи нет externalId, но разбор облака видит машину под облачным именем этого сервера: связь потеряна, сама машина скорее всего работает и тарифицируется. Удалять сервер и создавать новый НЕ нужно — тогда машина останется работать вообще без записи. Сервер в error не переводится, обратитесь в поддержку за восстановлением связи
502 WAKE_FAILED Во время пробуждения спящего сервера он перешёл в неожиданное состояние. Повторите запрос
502 PROVIDER_ERROR Облачный провайдер вернул ошибку при запуске виртуальной машины спящего сервера. Туннель здесь ни при чём — вызов /repair не поможет
409 GALAXY_APP_NOT_READY Только у galaxy-приложения (kind: "GALAXY_APP"): платформа не определила имя его контейнера на хосте и команду не запустила. Два условия. Имя контейнера не прошло проверку — в message приходит invalid on-host name. Либо у приложения нет субдомена или связи с galaxy-хостом — тогда в message приходит Galaxy app is missing its subdomain or host link. Текущее состояние приложения — GET /v1/infra/servers/:id
429 RATE_LIMITED Превышен лимит 10 операций в минуту на сервер. Точное значение — в заголовке x-ratelimit-limit (потолок делится на реплики)
503 EXEC_JOB_STORAGE_UNAVAILABLE Не удалось сохранить запись длительной операции или её адрес назначения; команда не отправлена
502 EXEC_JOB_CAPACITY На хосте достигнут предел 64 сохранённых каталогов длительных задач. Команда не запущена. Прочитайте error.operationId, дождитесь допустимой очистки после семи суток или обратитесь в поддержку. Автоматического повтора нет
502 EXEC_JOB_UNAVAILABLE Недоступно хранилище длительных задач, приём или запуск через systemd. Прочитайте error.operationId и обратитесь в поддержку; не заменяйте запрос синхронным запуском
502 EXEC_JOB_INVALID Агент отклонил параметры длительного запуска или истёкшее окно запуска. Команда не запущена. Прочитайте error.operationId; проверьте записанный отказ и обратитесь в поддержку при повторном отказе корректному запросу
502 EXEC_WORKDIR_DENIED Рабочая директория отдельной машины нарушает правила пути выше. Команда не выполнялась. Прочитайте error.operationId, затем выберите разрешённую существующую директорию
502 EXEC_JOB_REJECTED Агент отказал до исполнения без отдельного кода. Прочитайте отказ операции и обратитесь в поддержку; не заменяйте запрос синхронным запуском
502 UNKNOWN_TYPE Агент не поддерживает длительный exec; отказ до запуска, без перехода к синхронному исполнению
503 WAKE_TIMEOUT Спящий сервер не поднялся за отведённые 6.5 минуты — машина не запустилась или туннель не подключился. Сервер возвращается в sleeping, поэтому повторный запрос безопасен. См. «Известные особенности»

Отказы после приёма длительной задачи

HTTP 202 может предшествовать отказу, обнаруженному до запуска пользовательской команды. Опрашивайте возвращённую операцию; status: "failed" и data.error.code обозначают доказанный отказ, а unknown не разрешает новый exec. Истёкшее окно запуска или отказ рабочей директории описаны в таблице отказов длительного exec. Неизвестный исход имеет data.error: null; отсутствие кода ошибки не означает успех.

Ошибки во время синхронного выполнения

HTTP-статус не отражает результат команды. На отдельной виртуальной машине (kind: "STANDALONE") соединение удерживается на всё время выполнения, а статус 200 отправляется до его начала — поэтому и успешная, и провалившаяся команда приходят с кодом 200. Признак отказа — поле success: false и объект error в теле ответа. Проверяйте success в теле, а не HTTP-статус: клиент, который ветвится по статусу, посчитает провалившуюся команду успешной. У galaxy-приложения (kind: "GALAXY_APP") та же ошибка приходит со статусом 502. Исключений два, те же, что в таблице видов сервера выше: EXEC_BUSY приходит как 409, а EXEC_NO_EXIT — как 200 с success: false. Поэтому проверять success в теле нужно на обоих видах серверов.

Код Описание
EXEC_TIMEOUT Превышен timeout (или стандартные 300 секунд). Агент завершает процесс-группу принудительно (SIGKILL, без grace-паузы). В ответе — объект hint, его состав разобран ниже
EXEC_WORKDIR_DENIED Рабочая директория отдельной машины нарушает правила пути выше. Агент отказал до запуска команды; сверьте записанный исход перед использованием разрешённой директории
EXEC_WORKDIR_NOT_FOUND Рабочая директория отдельной машины отсутствует или недоступна для проверки. Агент отказал до запуска команды; сверьте записанный исход перед использованием существующей доступной директории
EXEC_WORKDIR_NOT_DIR Рабочий путь отдельной машины не является директорией. Агент отказал до запуска команды; сверьте записанный исход перед использованием директории
EXEC_FAILED Ошибка выполнения на агенте
EXEC_NO_EXIT Поток завершился, не прислав статус выхода. Что успела сделать команда на сервере — неизвестно, поэтому это не успех: агент мог перестать отвечать, туннель — оборваться в середине команды, а очень большой вывод — переполнить канал уже после того, как команда отработала. В data возвращается накопленный к моменту обрыва вывод (stdout, stderr). Полей exitCode, duration и truncated нет — их значения платформе неизвестны. Приходит на обоих видах серверов. На отдельной виртуальной машине (kind: "STANDALONE") ответ дополнительно несёт hint с путём восстановления, у galaxy-приложения его нет: подсказка ведёт на восстановление виртуальной машины, которой у приложения в галактике нет
EXEC_BUSY Канал агента занят синхронной командой, а на отдельной машине — также длительной задачей. Синхронный отказ приходит как success: false с HTTP 200 на STANDALONE либо 409 с Retry-After и retryable: true / retryAfter на GALAXY_APP. Его hint.recoveryAction сначала указывает на GET /v1/infra/servers/:id/operations?kind=exec. Прочитайте исходы длительных задач и опрашивайте работающие; при неизвестном исходе нужны поддержка и подтверждение полной остановки. Отсутствие autoExpiresInSeconds не доказывает утечку мьютекса. Через /unstick восстанавливайте только подтверждённо зависший синхронный мьютекс независимо подтверждённой простаивающей отдельной машины. Владелец galaxy-приложения не может восстановить общий канал хоста; задача соседнего приложения может не быть видна в его списке, поэтому при устойчивой занятости обратитесь в поддержку
CONTAINER_EXEC_OPTIONS_DENIED Синхронный exec Galaxy отклонил хостовые workdir или env до исполнения. Публичный API обычно отклоняет их раньше с 400 GALAXY_EXEC_NO_WORKDIR_ENV. Сверьте записанный исход и исправьте синхронный запрос
CONTAINER_NOT_READY Хост галактики ответил, но контейнера приложения, в котором должна была выполниться команда, на нём ещё нет: слот заведён и ни разу не выложен, выкладка идёт прямо сейчас или контейнер перезапускается. Приходит со статусом 502. В ответе hint, который ведёт к выкладке приложения, а не к хосту — хост исправен, будить и чинить его незачем. Только у galaxy-приложения (kind: "GALAXY_APP")

Состав `hint` у `EXEC_TIMEOUT`

Поле Тип Описание
hint.reason string Что произошло: команда не уложилась в отведённое время, и вся процесс-группа завершена немедленно, без grace-паузы
hint.recovery string Что делать: в синхронном режиме верхняя граница timeout — 600 секунд, для более длинных операций запускайте фоновую задачу (systemd-run --unit=<name>) и следите за ней через GET /v1/infra/servers/:id/logs с параметром ?service=<имя>, а для прогресса в пределах таймаута передайте ?stream=true
hint.recoveryAction string Указатель на документацию: docs: /docs/infra/deploy/exec

Подсказка приходит в JSON-ответе и SSE-событии error. Агентский EXEC_BUSY на обоих поддерживаемых видах сервера использует GET /v1/infra/servers/:id/operations?kind=exec как recoveryAction. Сначала прочитайте исход операции. /unstick нужен только при подтверждённой утечке синхронного мьютекса независимо подтверждённой простаивающей отдельной машины. На STANDALONE работающая длительная задача занимает канал после освобождения платформенного лока с 202. Опрашивайте её операцию; при неизвестном исходе нужны поддержка и независимое подтверждение полной остановки. Отсутствие autoExpiresInSeconds не доказывает утечку мьютекса. Восстановление туннеля и удаление лока не снимают ограничение приёма длительной задачи. В Galaxy команды выполняются синхронно с пределом 600 секунд и общим каналом для всех приложений и выкладок; задача соседнего приложения может не попасть в ваш список операций, поэтому пустой список не доказывает простой хоста.

Отказ связи с туннелем приходит в том же виде — со своим кодом в поле error.code (например TUNNEL_NOT_FOUND или GATEWAY_TIMEOUT: …). Часть gateway-кодов несёт детали после двоеточия (GATEWAY_UNREACHABLE: …, GATEWAY_TIMEOUT: …), поэтому сопоставляйте error.code по префиксу, а не по точному совпадению. В потоковом режиме (?stream=true) эти же ошибки приходят SSE-событием error. Данные события несут code и message, а у EXEC_TIMEOUT, GATEWAY_TIMEOUT и агентского EXEC_BUSY — ещё и hint. Подсказка у GATEWAY_TIMEOUT говорит главное: код возврата не получен, поэтому исход команды неизвестен и она может продолжать выполняться на сервере — не запускайте её повторно вслепую, сначала проверьте состояние. Поля success в них нет — признаком отказа служит само событие error.

Полный список общих ошибок API — Ошибки.

Известные особенности

  • Спящую отдельную виртуальную машину команда будит сама. Если сервер (kind: "STANDALONE") в статусе sleeping, платформа запускает пробуждение и ждёт его в том же запросе — до 6.5 минут, и только потом выполняет команду. Отдельный вызов /wake не нужен, но клиентский таймаут запроса это время обязан пережить. Если сервер не поднялся, приходит 503 WAKE_TIMEOUT и сервер возвращается в sleeping — команда не выполнялась, повторный запрос безопасен.
  • Команда исполняется через /bin/sh. Поле command запускается интерпретатором /bin/sh — это минимальный POSIX-шелл (dash), не bash. Конструкции, специфичные для bash (set -o pipefail, [[ … ]], массивы), в нём недоступны и завершаются ошибкой. Запускайте их явно — через command: "bash -c 'set -o pipefail; …'" либо файлом-скриптом command: "/bin/bash /opt/app/script.sh".
  • Блокировка на уровне сервера. Синхронный /exec и /deploy берут платформенный лок; окно лока синхронного exec — timeout + 90 секунд, deploy — 15 минут. Длительный exec отдельной машины освобождает этот лок после 202, но занимает свой канал на всё время задачи с запрошенным таймаутом до 86400 секунд. Проверка завершения может продлить занятость сверх таймаута команды. Неизвестный исход требует проверки и подтверждения остановки; /unstick и DELETE /lock не снимают ограничение длительной задачи. Exec Galaxy синхронный, ограничен 600 секундами, а канал хоста общий для всех приложений и выкладок. Зависший синхронный лок восстанавливайте только после независимого подтверждения простоя по состоянию сервера и логам.
  • Общий максимум вывода — 1 МиБ (1 048 576 байт), суммарно на stdout и stderr. Это один бюджет на два потока, а не по 1 МиБ на каждый: если команда пишет и в stdout, и в stderr, обрезка считается по их сумме. Когда вывод больше лимита, приходит truncated: true и хвост обрезается. Команда при этом отработала, exitCode корректный. Для большого вывода перенаправляйте в файл: command: "my-cmd > /opt/app/output.log 2>&1" и потом читайте через /exec cat /opt/app/output.log.
  • .env из /deploy не загружается автоматически. Systemd подгрузит .env при старте сервиса, но не для разовых команд через /exec. Если нужны DATABASE_URL / API-ключи — передавайте в поле env: { env: { DATABASE_URL: "..." } }.
  • Два таймаута в сумме дают timeout + 30 секунд. Агент убивает процесс ровно по timeout. Gateway ждёт ещё 30 секунд на финальные события и только потом отдаёт EXEC_TIMEOUT.
  • Поддержание соединения в JSON-режиме. Если команда выполняется дольше 15 секунд, сервер периодически шлёт пробелы в тело ответа — это предотвращает таймауты nginx и промежуточных прокси. Пробелы идут внутри уже открытого JSON-объекта, поэтому тело с первого байта выглядит как JSON ({), а не начинается с пробелов: и строгие клиенты, и обычные парсеры читают его одинаково, снимать пробелы вручную не нужно.
  • Клиентский таймаут: убедитесь, что он межбайтовый, а не общий. Keepalive-пробелы сбрасывают таймауты «на тишину», поэтому классические сокет-таймауты Python (urlopen(..., timeout=300), read-таймаут requests) сами по себе долгую команду в JSON-режиме не оборвут. Обрывают две другие вещи. Первая — клиенты и обёртки с настоящим wall-clock-дедлайном на весь запрос (например, aiohttp с ClientTimeout(total=…) или собственный дедлайн агент-фреймворка): keepalive против них бессилен. Вторая — серверный параметр timeout самой команды (по умолчанию 300 секунд): по его истечении агент завершает процесс и приходит EXEC_TIMEOUT независимо от клиентских настроек — не путайте с обрывом на своей стороне. Практика: в Python задавайте раздельные таймауты — requests.post(url, json=body, headers=headers, timeout=(10, 60)) — 10 секунд на connect и 60 на паузу между байтами. В синхронном режиме, если клиент оборвал соединение, лок на сервере держится ещё до timeout + 90 секунд (EXEC_BUSY для повторных вызовов). Команды дольше нескольких минут надёжнее запускать фоновой задачей — см. «Фоновые задачи» ниже. На спящем сервере это правило вступает в силу не сразу: пробелы идут только после пробуждения, поэтому до первого байта ответа проходит до 6.5 минут полной тишины, и рецепт timeout=(10, 60) оборвёт запрос ещё до запуска команды. Либо поднимите межбайтовый таймаут выше этого окна, либо разбудите машину заранее вызовом /wake с wait=true и вызывайте /exec уже на готовой.

Принудительное освобождение канала

На STANDALONE EXEC_BUSY без autoExpiresInSeconds может означать работающую длительную задачу, а не утечку мьютекса агента. Сначала прочитайте исход операции или найдите запуск в списке операций сервера с kind=exec. Пока задача работает, ждите. Исход unknown не подтверждает остановку: сверьте состояние и логи и обратитесь в поддержку. Не разрывайте туннель и не удаляйте лок работающей или неподтверждённой длительной задачи; /unstick не снимает её ограничение приёма. Используйте POST /v1/infra/servers/:id/unstick для восстановления лока только после независимого подтверждения простоя, с учётом ограничений этой ручки.

Как выбрать режим

Четыре режима вызова и когда каждый уместен:

Когда Как Мониторинг
Команда укладывается в 600 секунд, прогресс не нужен { "command": "...", "timeout": 600 } без ?stream=true — блокирующий JSON-ответ exitCode, stdout, stderr в теле ответа
Команда отдельной машины укладывается в timeout, нужен живой прогресс тот же запрос с ?stream=true — SSE-события stdout / stderr / exit читать события по мере поступления
Сохранённый результат отдельной машины; её канал занят на всё время задачи ?async=true, таймаут до 86400 только на STANDALONE Читайте statusUrl: исход и сохранённые stdout/stderr
Команда отдельной машины, которой нужно освободить канал до завершения фоновая задача под управлением клиента systemd-run --unit=<name> — /exec возвращается сразу GET /v1/infra/servers/:id/logs?service=<name>

Для синхронного exec общий дедлайн запроса на стороне клиента не ставьте меньше значения timeout команды — иначе клиент оборвёт соединение раньше, чем сервер успеет ответить. Спящий сервер добавляет к этому дедлайну до 6.5 минут на пробуждение — пример ниже рассчитан на уже запущенную машину. Пример раздельных таймаутов для Python:

Python
import requests
r = requests.post(
    f"{VIBE_URL}/v1/infra/servers/{SERVER_ID}/exec",
    headers={"X-Api-Key": VIBE_API_KEY},
    json={"command": "npm ci --production", "workdir": "/opt/app", "timeout": 600},
    timeout=(10, 60),   # 10 секунд на соединение, 60 на паузу между байтами
)

Поднять timeout у /exec шагу install не поможет — у шагов деплоя свой предел в 300 секунд, отдельный от синхронного предела 600 у /exec.

Важно: умолчание timeout — 300 секунд, а не 600. Синхронный потолок 600 существует, но чтобы им воспользоваться, поле надо передать явно. Команда, рассчитанная на девять минут и запущенная без timeout, будет снята на пятой.

Если ожидание оборвалось

Транспортный таймаут, разрыв соединения или перезапуск клиента прекращают только ожидание ответа. Команда на сервере при этом не останавливается и может доработать до конца уже после того, как клиент получил ошибку. Поэтому повторный запуск изменяющей команды может применить изменение второй раз.

Чтобы этого избежать, принятый запуск обычно получает идентификатор — но это не гарантия: если в синхронном режиме платформа не смогла завести запись, идентификатора не будет, а команда всё равно выполняется. В длительном режиме отдельной машины запись обязательна, и идентификатор возвращается с 202. Когда он есть, он приходит тремя каналами:

  • заголовок ответа X-Vibe-Operation-Id — на отдельной виртуальной машине уходит до начала выполнения команды, поэтому доживает до клиента даже когда тело ответа потеряно. У синхронного exec galaxy-приложения он приходит только с терминальным ответом: эта ветка не открывает поток, поэтому при обрыве ожидания идентификатор до клиента не доезжает и запуск ищется в списке операций сервера с ?kind=exec,
  • первый кадр event: operation при ?stream=true — только у синхронного exec на отдельной виртуальной машине. Браузерный EventSource заголовков ответа не отдаёт, поэтому для него это единственный ранний канал,
  • поле operationId в теле — data.operationId при успехе, error.operationId при отказе.

Порядок восстановления:

  1. Не повторяйте изменяющую команду вслепую.
  2. Запросите GET /v1/infra/operations/:operationId. Если идентификатор потерян, найдите запуск в списке операций сервера с параметром ?kind=exec.
  3. Статус succeeded означает, что команда отработала до конца, а её собственный вердикт лежит в exitCode. Повторять не нужно.
  4. Статус failed платформа ставит, только когда может доказать, что команда не запускалась, — например при EXEC_BUSY. Это доказывает только отказ этой попытке. При EXEC_BUSY сначала сверьте занявшую канал операцию, включая прежнюю попытку с потерянным подтверждением; неизвестный исход занятой операции не разрешает новый exec.
  5. Статус unknown означает, что исход не доказан и команда может выполняться прямо сейчас. Сверьтесь с состоянием сервера и журналом до решения о повторе.

Для синхронного exec запись хранит исход попытки — статус, код возврата и причину отказа — без вывода команды. С async=true stdout/stderr сохраняются в операции; другой вариант — самостоятельно управлять фоновой задачей и читать журнал.

Фоновые задачи

Для синхронного JSON и потокового exec timeout ограничен 600 секундами, а по его истечении агент завершает всю процесс-группу принудительно (SIGKILL, без grace-паузы). На отдельной машине для более долгих операций — установки пакетов, восстановления дампа БД, тяжёлой сборки — используйте долговременный async=true, если нужен сохранённый вывод. Другой вариант — самостоятельно управлять фоновой задачей: /exec вернётся мгновенно, канал освободится (второй вызов не упрётся в EXEC_BUSY), а процесс продолжит работать независимо от HTTP-соединения.

Рекомендуемый способ — транзиентный systemd-юнит

JSON
{ "command": "systemctl reset-failed restore-db 2>/dev/null; systemd-run --unit=restore-db /bin/bash /opt/data/restore.sh", "timeout": 30 }

Задача получает собственный cgroup и живёт независимо от exec-сессии. Управление — стандартными вызовами:

  • статус: { "command": "systemctl is-active restore-db" } — active (идёт), inactive (завершилась успешно), failed (упала).
  • логи: journald подхватывает вывод юнита автоматически — читайте через GET /v1/infra/servers/:id/logs?service=restore-db.
  • systemctl reset-failed <имя> перед повторным запуском — иначе systemd откажет в создании юнита с именем упавшей задачи.

Лёгкий вариант — фон с редиректом вывода

JSON
{ "command": "(cd /opt/app && npm run build > /tmp/build.log 2>&1 &)", "timeout": 10 }

Редирект обоих потоков в файл обязателен — он освобождает пайпы exec-сессии. Прогресс: { "command": "tail -20 /tmp/build.log" }.

Анти-паттерн — голый `nohup`

nohup cmd & без редиректа не работает: в пайпе (а не терминале) nohup не перенаправляет вывод, фоновый процесс наследует пайп exec-сессии, агент ждёт его закрытия до самого timeout — и затем принудительно завершает всю процесс-группу, включая ваш «фоновый» процесс. Всегда добавляйте > файл 2>&1.

Не вставляйте значения секретов в command. На отдельной машине передавайте их через env; в galaxy-приложении загружайте внутри контейнера из существующего защищённого файла или настроенного хранилища секретов приложения. Синхронный exec журналирует на агенте первые 200 символов команды. Длительный worker отключает этот журнал, но сама команда может раскрыть секреты через вывод или дочерние процессы. Помните также, что шаги install/preStart у /deploy имеют собственный таймаут 300 секунд — тяжёлые установки выносите в разовый /exec или фоновую задачу, а не в install-скрипт.

Durable exec — длительная команда

Для долгой команды вызовите POST /v1/infra/servers/:id/exec?async=true только на сервере STANDALONE. Ответ: HTTP 202, X-Vibe-Operation-Id и data: {operationId, status: "running", statusUrl}. Опрашивайте GET /v1/infra/operations/:operationId; после потери подтверждения не отправляйте второй /exec. Если идентификатор потерян, найдите запуск в списке операций сервера с kind=exec.

Тело принимает timeout от 1 до 86400 секунд (по умолчанию 300). async=true несовместим с stream=true. Любой другой вид сервера получает 400 DURABLE_EXEC_UNSUPPORTED_SERVER_KIND до блокировки, создания операции и отправки команды, без синхронного перехода. Exec Galaxy остаётся синхронным с пределом 600 секунд. Исполнение, не поддерживающее длительные задачи, отказывает до запуска; синхронного перехода нет.

Задача переживает потерю HTTP-ответа и переподключение. Её канал занят на всё время задачи, хотя после 202 платформенный лок освобождён; другие exec и команды выкладки получают EXEC_BUSY. Читайте операцию вместо повторного exec или вызова /unstick. Завершённая операция отдаёт stdout, stderr, duration в миллисекундах, truncated и доказанный exitCode; общий предел вывода — 1 МиБ. Вывод хранится зашифрованным в семидневном окне исхода. Приватный вывод доступен только ключу в области операции, принадлежащему пользователю-создателю: ротация ключа того же владельца сохраняет доступ, передача другому владельцу — нет. succeeded включает ненулевой код выхода; unknown не разрешает повтор команды. Перезагрузка машины или потеря процесса могут оставить неизвестный исход, требующий проверки и подтверждения остановки. Чтение сверяет результат без запуска команды. Отмена и автоматический повтор недоступны.

Исполнение хранит не более 64 записей задач на отдельной машине, включая завершённые задачи в семидневном окне хранения. При заполнении новая команда отклоняется до исполнения с HTTP 502 EXEC_JOB_CAPACITY и error.operationId. Сначала прочитайте операцию, затем дождитесь допустимой очистки по сроку хранения или обратитесь в поддержку; автоматического повтора нет. Очистка при приёме запроса удаляет только просроченные результаты с подтверждённой остановкой. Если задача прервана до сохранения результата, подтверждённая остановка оставляет исход unknown, но через семь суток запись допускается к штатной очистке. Активные задачи и неподтверждённая остановка не очищаются только потому, что прошло семь суток. /unstick и удаление лока не снимают ограничение приёма длительной задачи.

Смотрите также