Для 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 в ответе нет: пауза перед повтором
не делает повтор безопасным.
Алгоритм для чтения
- Распознайте код ответа.
- Для ошибки авторизации, бизнес-ошибки или circuit breaker сначала устраните причину.
- Для временного отказа выдержите
Retry-After; если его нет, примените ограниченный экспоненциальный backoff со случайной добавкой. - Повторите чтение не более заданного клиентом числа раз.
- При устойчивом отказе остановитесь и передайте поддержке время и
X-Request-Id, если он был в ответе.
Алгоритм для записи
- Не используйте единый список retryable-кодов для чтения и записи.
- Если ячейка «Запись» разрешает повтор, дождитесь указанного условия и повторяйте с ограничением числа попыток.
- Если требуется перечитать состояние, найдите именно бизнес-эффект исходной операции по уникальному признаку.
- Если эффект уже есть, считайте исходную запись успешной и не повторяйте её.
- Если эффект достоверно отсутствует, выполните новую попытку.
- Если проверка ненадёжна, остановите автоматический повтор и разрешите случай вручную.
Обычный GET без уникального критерия не доказывает отсутствие дубля. Если метод имеет собственный серверный ключ идемпотентности, можно опираться на контракт этого метода; эта страница сама такой гарантии не вводит.
Пример классификатора
Классификатор принимает вид операции явно. Он не пытается вывести его из HTTP-
глагола и не реализует универсальный readBack: проверка эффекта зависит от
конкретной сущности.
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 у общего конверта не означает успех всех элементов.