لوكلاء AI: ملف markdown لهذه الصفحة — /docs-content-en/errors/retry-safety.md فهرس التوثيق — /llms.txt
مقالات التوثيق متاحة حاليًا باللغة الإنجليزية فقط.
What is safe to retry
The HTTP status and the word retry alone do not answer the main question: will
a retry create a duplicate record? Answering that needs two separate
characteristics — whether the target call of the method managed to reach
Bitrix24, and whether the original request is a read or a write.
Retry-After tells you when the next attempt is allowed from a load
standpoint. It does not prove that the previous write was not applied.
Terms
Target call
The target call is the Bitrix24 business method you wanted to execute, for
example crm.deal.get or crm.deal.add. A service-level OAuth token refresh
does not count as a target call.
For BH_APP_TIMEOUT, the outcome is unknown. The code can come from the agent waiting 14 seconds for headers after sending the request or exhausting its 20-second total budget for a small response; from the Gateway waiting 30 seconds for a first frame or 15 seconds for a pre-commit stream continuation; or from the app itself returning 504 without Content-Type, a shape indistinguishable from an agent refusal. The app may have received the request and called Bitrix24. The code alone cannot establish whether it did.
Read and write
A read does not change data: retrying it is generally safe from a duplicate
standpoint. A write creates, changes, deletes, or sends data, so it needs a
separate verdict. Determine the operation type by the meaning of the method,
not the HTTP verb: a POST can perform a search, while methods like *.add,
*.update, *.delete, and *.send change state.
Unknown outcome
"Unknown" means the code alone cannot prove whether the target call managed to produce an effect. The absence of a successful response does not equal the absence of an effect.
Decision rule
- Retry is safe — wait for the stated condition and retry with a limit on the number of attempts.
- Read back first — locate the operation's effect by a stable business key or identifier. Retry the write only if you can prove the effect is absent.
- Do not retry automatically — fix the cause, or hand the case to a human. After the input changes, it is a new deliberate attempt, not a retry of the same request.
If you cannot reliably verify the write's effect, "read back first" means "do
not retry automatically". X-Request-Id is for diagnostics and is not an
idempotency key.
Code matrix
| Code | Did the target call reach Bitrix24 | Read | Write | Condition and explanation |
|---|---|---|---|---|
CodeQUEUE_OVERFLOW |
Did the target call reach Bitrix24No | ReadRetry is safe | WriteRetry is safe | Condition and explanationHonor Retry-After, lower concurrency, use backoff with jitter. The queue rejected the call before it ran. |
CodeQUEUE_TIMEOUT |
Did the target call reach Bitrix24No | ReadRetry is safe | WriteRetry is safe | Condition and explanationHonor Retry-After. The call was removed from the queue before execution started. |
CodeRATE_LIMITED |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRetry is safe | Condition and explanationHonor Retry-After. The code covers both local pre-call limits and a Bitrix24 rate rejection without the operation being applied. |
CodeCALLER_RATE_LIMITED |
Did the target call reach Bitrix24No | ReadRetry is safe | WriteRetry is safe | Condition and explanationHonor Retry-After — until the one-minute window ends the same refusal repeats. The platform refused before the operation started. |
CodeOPERATION_TIME_LIMIT |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationStrictly honor Retry-After. The first rejection comes from a method already called on Bitrix24; subsequent ones may occur locally before the call is sent. |
CodeTIMEOUT_QUARANTINE |
Did the target call reach Bitrix24No | ReadRetry is safe | WriteRetry is safe | Condition and explanationDo not retry before the full Retry-After elapses: early requests interfere with recovery. The current call was stopped locally before it was sent. |
CodeBITRIX_TIMEOUT |
Did the target call reach Bitrix24Yes | ReadRetry is safe | WriteRead back first | Condition and explanationWait for the recommended pause. The platform stopped waiting for the response, but processing of the write on Bitrix24 may have continued. |
CodeBITRIX_ERROR |
Did the target call reach Bitrix24Yes | ReadRetry is safe after fixing the cause | WriteDo not retry automatically | Condition and explanationFix the cause using b24Code, message, and the error fields. A plain pause does not change the outcome; for a questionable write, additionally verify the state. |
CodeBITRIX_UNAVAILABLE |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationUse bounded exponential backoff. The code covers upstream 5xx and network failures; the absence of a response does not prove the absence of an effect. |
CodePOOL_EXHAUSTED |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationHonor Retry-After. The shared DB error handler can fire at different stages of request processing. |
CodeDB_TRANSIENT |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationHonor Retry-After. Rolling back the local DB transaction does not roll back a Bitrix24 call that already happened. |
CodeSERVICE_UNAVAILABLE |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationHonor Retry-After. The platform boundary does not know whether the backend managed to complete the external effect; in particular, a 504 does not stop the upstream call. |
CodeTOKEN_REFRESH_FAILED |
Did the target call reach Bitrix24No | ReadRetry is safe after authorization is restored | WriteRetry is safe after authorization is restored | Condition and explanationThe target method was not sent. A plain backoff without fixing authorization is useless. |
CodeERROR_LOOP_DETECTED |
Did the target call reach Bitrix24No | ReadRetry is safe after the cause is resolved | WriteDo not retry automatically | Condition and explanationThe current call was stopped by the circuit breaker before it was sent. Fix the stable cause first; occasional probe calls may get through. |
CodeBH_APP_STARTING |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationNo response frame arrived from the app at all, or the agent answered for itself rather than the app — both say a frame from the app was missing, but neither proves the app's code never saw the request: the connection could have dropped after processing had already started. Wait for Retry-After. |
CodeBH_APP_TIMEOUT |
Did the target call reach Bitrix24Unknown | ReadRetry is safe | WriteRead back first | Condition and explanationSources: the agent's 14-second header window or 20-second total budget for a small response, the Gateway's 30-second first-frame or 15-second pre-commit idle window, or an app response of 504 without Content-Type. The request may have run. There is no Retry-After: waiting does not make retrying safe. |
CodeAGENT_WRITE_ALREADY_EXECUTED |
Did the target call reach Bitrix24No | Read— | WriteRead back first | Condition and explanationArrives only for an AI agent key on a repeated DELETE to the same address within 30 seconds, never on reads. If error.firstStatus holds a success code, the first delete was executed — treat the operation as done. null means it is still running: read the record back. |
CodeLARGE_BODY_BACKEND_BUSY |
Did the target call reach Bitrix24No | ReadRetry is safe | WriteRetry is safe | Condition and explanationHonor Retry-After. The admission gate rejected the request before the route handler and the target call. |
Why some codes are ambiguous
`RATE_LIMITED`
Delivery is unknown because the same code is shared by local limits and by Bitrix24. Retrying a write after a pause is still safe: the known sources either never send the call or unambiguously reject it by rate without the operation being applied. This does not mean every HTTP 429 response always occurs before the call is sent.
`OPERATION_TIME_LIMIT`
The first rejection with this code is returned by a Bitrix24 method that was already called. After it, Vibecode remembers the pause and may reject following requests before they are sent. That is why the write path takes the more conservative action — verify the effect first.
`TIMEOUT_QUARANTINE`
The current request, stopped by the local pause, is safe. An earlier operation whose timeout triggered the quarantine may have an unknown outcome — this guarantee does not extend to it.
`DB_TRANSIENT`
A transaction rollback in the Vibecode database is not a distributed rollback of Bitrix24. If the external call already completed, the local rollback does not undo its effect.
`BH_APP_STARTING`
No response frame arrived from the app at all, or the agent answered for
itself rather than the app. Both say a frame from the app was missing, but
neither proves the app's code never saw the request: the connection could
have dropped after processing had already started. That is why a write
requires reading state back first rather than a blind retry — unlike
BH_APP_TIMEOUT below, this code does carry a Retry-After.
`BH_APP_TIMEOUT`
This response can come from the agent waiting 14 seconds for headers after sending the request, exhausting its 20-second total budget for a small buffered response, the Gateway waiting 30 seconds for the first frame or 15 seconds for a pre-commit stream continuation. The app's own 504 without Content-Type has the same shape and becomes this code too. None of these sources proves that no side effect occurred: the app may have called Bitrix24. There is no Retry-After; check the result of a write before retrying it.
Algorithm for reads
- Recognize the response code.
- For an authorization error, a business error, or a circuit breaker, fix the cause first.
- For a transient failure, honor
Retry-After; if it is absent, apply bounded exponential backoff with jitter. - Retry the read no more than the number of times set by the client.
- On a persistent failure, stop and pass the time and
X-Request-Id(if it was present in the response) to support.
Algorithm for writes
- Do not use a single retryable-codes list for both reads and writes.
- If the "Write" cell allows a retry, wait for the stated condition and retry with a limit on the number of attempts.
- If you need to read back the state, locate exactly the business effect of the original operation by a unique attribute.
- If the effect is already there, treat the original write as successful and do not retry it.
- If the effect is verifiably absent, perform a new attempt.
- If the check is unreliable, stop the automatic retry and resolve the case manually.
A plain GET without a unique criterion does not prove the absence of a duplicate. If the method has its own server-side idempotency key, you can rely on that method's contract; this page itself does not introduce that guarantee.
Classifier example
The classifier takes the operation kind explicitly. It does not try to infer
it from the HTTP verb and does not implement a universal readBack: verifying
the effect depends on the specific entity.
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', 'AGENT_WRITE_ALREADY_EXECUTED',
]);
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', 'CALLER_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';
}
Handle a code the classifier does not recognize conservatively: a read can be retried with bounded backoff, a write only after verifying the state. A network error without the platform's JSON envelope has the same unknown outcome for a write.
In a batch, the decision is made separately for each item by its own code and the sub-request's semantics. An HTTP 200 on the shared envelope does not mean every item succeeded.