Please read this first
Describe the bug
BackendSpanExporter._export_with_deadline() treats every 4xx response as final:
# 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
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.
Please read this first
BackendSpanExporterreference.BackendSpanExporter._shutdown_eventis never cleared, permanently disabling retries after one shutdown #4683 / Fix tracing exporter reuse after shutdown #4711 cover retries being disabled after shutdown, BackendSpanExporter: one metadata or custom span value that isn't JSON serializable drops the whole export batch, including unrelated traces #4983 covers a non-serializable value dropping a batch, Tracing exporter honours OPENAI_API_KEY/ORG/PROJECT from the environment but not the endpoint, so OPENAI_BASE_URL does not redirect traces #5008 covers the endpoint. None covers how the exporter classifies HTTP status codes.Describe the bug
BackendSpanExporter._export_with_deadline()treats every4xxresponse as final:That includes
429 Too Many Requests. A single rate-limited response from/v1/traces/ingestdiscards the whole batch, up tomax_batch_size(128) traces and spans, with no retry and without looking atRetry-After. The run itself succeeds, so the only symptom is a gap in the dashboard and one[non-fatal]log line. The same applies to408and409, which the OpenAI Python client (_base_client._should_retry) retries together with429and5xx; the exporter only mirrors the5xxhalf of that policy.The export path already has everything needed to retry:
max_retries,base_delay,max_delay, the backoff loop, andagents.models._retry_runtime.parse_retry_after_value/parse_retry_after_msfor the header.Debug information
mainatfbf59a40(also 0.22.0)httpx2.Clientis patchedRepro steps
Expected behavior
408,409and429are retried with the existing backoff up tomax_retries, waiting at least the advertisedRetry-After/retry-after-ms(bounded bymax_delay), the way the OpenAI client and the SDK's own model retry helpers already treat these statuses. Other4xxresponses stay non-retried. In the script above the second call succeeds andpost callsis 2.A fix with regression tests is ready and I will attach it right away.