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

Что безопасно повторять

HTTP-статус и слово retry сами по себе не отвечают на главный вопрос: не создаст ли повтор записи дубль. Для решения нужны две отдельные характеристики — успел ли целевой вызов метода уйти в Битрикс24 и является исходный запрос чтением или записью.

Retry-After указывает, когда допустима следующая попытка с точки зрения нагрузки. Он не доказывает, что предыдущая запись не применилась.

Термины

Целевой вызов

Целевой вызов — бизнес-метод Битрикс24, который вы хотели выполнить, например crm.deal.get или crm.deal.add. Служебное обновление OAuth-токена целевым вызовом не считается.

Для BH_APP_TIMEOUT запрос мог дойти до приложения, а мог и не дойти: шлюз знает только то, что отдал кадр запроса в туннель, а не то, что приложение его приняло — агент мог не суметь подключиться к порту приложения. Шлюз лишь перестал ждать ответ по собственному таймауту, а не потому что приложение отказало. Поэтому исход бизнес-операции по одному коду неизвестен: если запрос дошёл, приложение могло успеть вызвать Битрикс24 до и после того, как шлюз прекратил ждать.

Чтение и запись

Чтение не меняет данные: его повтор обычно безопасен с точки зрения дублей. Запись создаёт, изменяет, удаляет или отправляет данные, поэтому для неё нужен отдельный вывод. Определяйте тип операции по смыслу метода, а не по HTTP-глаголу: POST может выполнять поиск, а методы *.add, *.update, *.delete и *.send меняют состояние.

Неизвестный исход

«Неизвестно» означает, что по коду нельзя доказать, успел ли целевой вызов создать эффект. Отсутствие успешного ответа не равно отсутствию эффекта.

Правило принятия решения

  • Повтор безопасен — дождитесь указанного условия и повторяйте с ограничением числа попыток.
  • Сначала перечитать — найдите эффект операции по устойчивому бизнес-ключу или идентификатору. Повторяйте запись только если можете доказать, что эффекта нет.
  • Не повторять автоматически — исправьте причину либо передайте случай человеку. После изменения входных данных это новая осознанная попытка, а не retry того же запроса.

Если надёжно проверить эффект записи невозможно, «сначала перечитать» означает «не повторять автоматически». X-Request-Id нужен для диагностики и не является ключом идемпотентности.

Матрица кодов

Код Целевой вызов дошёл до Битрикс24 Чтение Запись Условие и пояснение
QUEUE_OVERFLOW Нет Повтор безопасен Повтор безопасен Выдержать Retry-After, снизить параллелизм, использовать backoff со случайной добавкой. Очередь отказала до запуска вызова.
QUEUE_TIMEOUT Нет Повтор безопасен Повтор безопасен Выдержать Retry-After. Вызов удалён из очереди до начала исполнения.
RATE_LIMITED Неизвестно Повтор безопасен Повтор безопасен Выдержать Retry-After. Код объединяет локальные ограничения до вызова и отказ Битрикс24 по частоте без применения операции.
OPERATION_TIME_LIMIT Неизвестно Повтор безопасен Сначала перечитать Строго выдержать Retry-After. Первый отказ приходит от уже вызванного метода Битрикс24, последующие могут возникать локально до отправки.
TIMEOUT_QUARANTINE Нет Повтор безопасен Повтор безопасен Не повторять раньше полного Retry-After: досрочные запросы мешают восстановлению. Текущий вызов остановлен локально до отправки.
BITRIX_TIMEOUT Да Повтор безопасен Сначала перечитать Дождаться рекомендуемой паузы. Платформа прекратила ждать ответ, но обработка записи в Битрикс24 могла продолжиться.
BITRIX_ERROR Да Повтор безопасен после исправления причины Не повторять автоматически Исправить причину по b24Code, message и полям ошибки. Простая пауза не меняет результат; для сомнительной записи дополнительно проверить состояние.
BITRIX_UNAVAILABLE Неизвестно Повтор безопасен Сначала перечитать Использовать ограниченный экспоненциальный backoff. Код покрывает upstream 5xx и сетевые сбои; отсутствие ответа не доказывает отсутствие эффекта.
POOL_EXHAUSTED Неизвестно Повтор безопасен Сначала перечитать Выдержать Retry-After. Общий обработчик ошибки БД может сработать на разных этапах обработки запроса.
DB_TRANSIENT Неизвестно Повтор безопасен Сначала перечитать Выдержать Retry-After. Откат локальной транзакции БД не откатывает уже совершённый вызов Битрикс24.
SERVICE_UNAVAILABLE Неизвестно Повтор безопасен Сначала перечитать Выдержать Retry-After. Граница платформы не знает, успел ли backend завершить внешний эффект; в частности, 504 не останавливает upstream.
TOKEN_REFRESH_FAILED Нет Повтор безопасен после восстановления авторизации Повтор безопасен после восстановления авторизации Целевой метод не отправлен. Один backoff без ремонта авторизации бесполезен.
ERROR_LOOP_DETECTED Нет Повтор безопасен после устранения причины Не повторять автоматически Текущий вызов остановлен circuit breaker до отправки. Сначала исправить стабильную причину; редкие probe-вызовы могут уйти дальше.
BH_APP_STARTING Неизвестно Повтор безопасен Сначала перечитать Кадр от приложения не пришёл вовсе, либо ответил сам агент, а не приложение — оба случая говорят об отсутствии кадра от приложения, но не доказывают, что код приложения его не увидел: соединение могло оборваться уже во время начатой обработки. Подождать Retry-After.
BH_APP_TIMEOUT Неизвестно Повтор безопасен Сначала перечитать Шлюз прекращает ждать ответ по одному из двух окон (30 секунд без единого кадра либо начатый ответ, замолчавший дольше 15 секунд) — не потому что приложение отказало. Запрос мог быть доставлен приложению и выполниться, а мог и не дойти: шлюз знает только то, что отдал кадр в туннель. Retry-After в ответе нет: пауза не делает повтор безопасным.
LARGE_BODY_BACKEND_BUSY Нет Повтор безопасен Повтор безопасен Выдержать Retry-After. Admission-гейт отказал до route handler и целевого вызова.

Почему некоторые коды неоднозначны

`RATE_LIMITED`

Доставка неизвестна, потому что один код используют и локальные ограничения, и Битрикс24. Повтор записи после паузы всё же безопасен: известные источники либо не отправляют вызов, либо однозначно отказывают по частоте без применения операции. Это не означает, что любой ответ HTTP 429 всегда возникает до отправки.

`OPERATION_TIME_LIMIT`

Первый отказ с этим кодом возвращает уже вызванный метод Битрикс24. После него Вайбкод запоминает паузу и может отбивать следующие запросы до отправки. Поэтому для записи применяется более консервативное действие — сначала проверить эффект.

`TIMEOUT_QUARANTINE`

Безопасен текущий запрос, остановленный локальной паузой. Операция, чей более ранний таймаут включил карантин, могла иметь неизвестный исход — к ней это обещание не относится.

`DB_TRANSIENT`

Откат транзакции в базе Вайбкод не является распределённым откатом Битрикс24. Если внешний вызов уже завершился, локальный rollback не отменит его эффект.

`BH_APP_STARTING`

Кадр от приложения не пришёл вовсе, либо ответил сам агент, а не приложение. Оба случая говорят об отсутствии кадра от приложения, но не доказывают, что код приложения его не увидел: соединение могло оборваться уже во время начатой обработки. Поэтому запись требует сначала перечитать состояние, а не повторяется вслепую — в отличие от BH_APP_TIMEOUT ниже, у этого кода есть Retry-After.

`BH_APP_TIMEOUT`

Этот ответ формируется, когда шлюз перестаёт ждать ответ приложения по одному из двух окон: 30 секунд без единого кадра, либо начатый ответ (заголовки уже пришли) замолчал дольше 15 секунд. Ни то ни другое не означает, что запрос гарантированно дошёл до приложения — шлюз знает только то, что отдал кадр в туннель, а не то, что приложение его приняло. Если запрос всё же дошёл, приложение могло успеть вызвать Битрикс24, поэтому исход операции по коду BH_APP_TIMEOUT неизвестен, а Retry-After в ответе нет: пауза перед повтором не делает повтор безопасным.

Алгоритм для чтения

  1. Распознайте код ответа.
  2. Для ошибки авторизации, бизнес-ошибки или circuit breaker сначала устраните причину.
  3. Для временного отказа выдержите Retry-After; если его нет, примените ограниченный экспоненциальный backoff со случайной добавкой.
  4. Повторите чтение не более заданного клиентом числа раз.
  5. При устойчивом отказе остановитесь и передайте поддержке время и X-Request-Id, если он был в ответе.

Алгоритм для записи

  1. Не используйте единый список retryable-кодов для чтения и записи.
  2. Если ячейка «Запись» разрешает повтор, дождитесь указанного условия и повторяйте с ограничением числа попыток.
  3. Если требуется перечитать состояние, найдите именно бизнес-эффект исходной операции по уникальному признаку.
  4. Если эффект уже есть, считайте исходную запись успешной и не повторяйте её.
  5. Если эффект достоверно отсутствует, выполните новую попытку.
  6. Если проверка ненадёжна, остановите автоматический повтор и разрешите случай вручную.

Обычный GET без уникального критерия не доказывает отсутствие дубля. Если метод имеет собственный серверный ключ идемпотентности, можно опираться на контракт этого метода; эта страница сама такой гарантии не вводит.

Пример классификатора

Классификатор принимает вид операции явно. Он не пытается вывести его из HTTP- глагола и не реализует универсальный readBack: проверка эффекта зависит от конкретной сущности.

javascript
const VERIFY_WRITE_STATE = new Set([
  'OPERATION_TIME_LIMIT', 'BITRIX_TIMEOUT', 'BITRIX_UNAVAILABLE',
  'POOL_EXHAUSTED', 'DB_TRANSIENT', 'SERVICE_UNAVAILABLE', 'BH_APP_STARTING',
  'BH_APP_TIMEOUT',
]);

const FIX_BEFORE_RETRY = new Set([
  'BITRIX_ERROR', 'TOKEN_REFRESH_FAILED', 'ERROR_LOOP_DETECTED',
]);

const SAFE_RETRY = new Set([
  'QUEUE_OVERFLOW', 'QUEUE_TIMEOUT', 'RATE_LIMITED',
  'TIMEOUT_QUARANTINE', 'LARGE_BODY_BACKEND_BUSY',
]);

function classifyRetry(code, operationKind) {
  if (FIX_BEFORE_RETRY.has(code)) return 'fix_before_retry';
  if (operationKind === 'write' && VERIFY_WRITE_STATE.has(code)) {
    return 'state_verification_required';
  }
  if (SAFE_RETRY.has(code) || operationKind === 'read') return 'retry_with_limit';
  return 'state_verification_required';
}

Неизвестный классификатору код обрабатывайте консервативно: чтение можно повторить с ограниченным backoff, запись — только после проверки состояния. Ошибка сети без JSON-конверта платформы имеет тот же неизвестный исход для записи.

В batch решение принимается отдельно для каждого элемента по его коду и семантике подзапроса. HTTP 200 у общего конверта не означает успех всех элементов.

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