BackendSpanExporter drops the whole batch on HTTP 429 instead of retrying with Retry-After

Author: coderdailyoneCreated Sep 14, 2026Updated Sep 15, 2026

Please read this first

  • Have you read the docs? Yes: Tracing and the BackendSpanExporter reference.
  • Have you searched for related issues? Yes. #4683 / #4711 cover retries being disabled after shutdown, #4983 covers a non-serializable value dropping a batch, #5008 covers the endpoint. None covers how the exporter classifies HTTP status codes.

Describe the bug

BackendSpanExporter._export_with_deadline() treats every 4xx response as final:

python
# If the response is a client error (4xx), we won't retry
if 400 <= response.status_code < 500:
    logger.error("[non-fatal] Tracing client error %s: %s", ...)
    break

That includes 429 Too Many Requests. A single rate-limited response from /v1/traces/ingest discards the whole batch, up to max_batch_size (128) traces and spans, with no retry and without looking at Retry-After. The run itself succeeds, so the only symptom is a gap in the dashboard and one [non-fatal] log line. The same applies to 408 and 409, which the OpenAI Python client (_base_client._should_retry) retries together with 429 and 5xx; the exporter only mirrors the 5xx half of that policy.

The export path already has everything needed to retry: max_retries, base_delay, max_delay, the backoff loop, and agents.models._retry_runtime.parse_retry_after_value / parse_retry_after_ms for the header.

Debug information

  • Agents SDK version: main at fbf59a40 (also 0.22.0)
  • Python version: 3.10.12
  • Operating system: Linux
  • Model and model provider: none needed; httpx2.Client is patched
  • Does the issue reproduce with the latest Agents SDK release? Yes
  • Does the issue occur consistently or intermittently? Consistently for any 429
ERROR:openai.agents:[non-fatal] Tracing client error 429. Response data is redacted.
post calls: 1

Repro steps

python
import logging
from unittest.mock import MagicMock, patch

from agents.tracing.processors import BackendSpanExporter
from agents.tracing.spans import SpanImpl
from agents.tracing.span_data import CustomSpanData

logging.basicConfig(level=logging.WARNING)

with patch("httpx2.Client") as client:
    limited = MagicMock(status_code=429, headers={"retry-after": "1"})
    limited.text = '{"error": {"message": "Rate limit reached"}}'
    ok = MagicMock(status_code=200)
    client.return_value.post.side_effect = [limited, ok]

    exporter = BackendSpanExporter(api_key="sk-test", max_retries=3, base_delay=0.1)
    span = SpanImpl(
        trace_id="trace_1",
        span_id="span_1",
        parent_id=None,
        processor=MagicMock(),
        span_data=CustomSpanData(name="probe", data={}),
        tracing_api_key=None,
    )
    exporter.export([span])
    print("post calls:", client.return_value.post.call_count)  # 1: the batch is gone

Expected behavior

408, 409 and 429 are retried with the existing backoff up to max_retries, waiting at least the advertised Retry-After / retry-after-ms (bounded by max_delay), the way the OpenAI client and the SDK's own model retry helpers already treat these statuses. Other 4xx responses stay non-retried. In the script above the second call succeeds and post calls is 2.

A fix with regression tests is ready and I will attach it right away.

Source: openai/openai-agents-python