Skip to content

BackendSpanExporter: one metadata or custom span value that isn't JSON serializable drops the whole export batch, including unrelated traces #4983

Description

@Om-singhaI

Describe the bug

BackendSpanExporter posts a whole batch of traces and spans in one request. If any one item in that batch has a value json can't encode, the request body can't be built, export() raises, and BatchTraceProcessor drops the entire batch. So one bad value in one run's trace_metadata also throws away every other trace and span that happened to be queued with it, including runs that had nothing wrong with them.

Things that trigger it: a uuid.UUID or datetime in RunConfig(trace_metadata=...), a datetime in custom_span(data=...), or a NaN or inf float (httpx2 encodes with allow_nan=False). It happens on the default OpenAI ingest endpoint and on custom endpoints.

Debug information

  • Agents SDK version: 0.22.2 (also main at fbd2dbc)
  • Python version: 3.14.3
  • Operating system: macOS
  • Model and model provider: none needed, the repro uses ScriptedModel
  • Does the issue reproduce with the latest Agents SDK release? Yes
  • Does the issue occur consistently or intermittently? Consistently
ERROR [non-fatal] Tracing exporter failed; dropping batch of 8 items
items received: 0 []

Calling the exporter directly shows the cause:

exporter.export([trace("x", metadata={"request_id": uuid.uuid4()})])
TypeError: Object of type UUID is not JSON serializable

Repro steps

The repro doesn't touch the network. httpx2.MockTransport stands in for the ingest endpoint.

import asyncio, json, uuid, logging
import httpx2
from agents import Agent, Runner, RunConfig
from agents.testing.model import ScriptedModel, assistant_message
from agents.tracing import set_trace_processors
from agents.tracing.processors import BackendSpanExporter, BatchTraceProcessor

logging.basicConfig(level=logging.ERROR, format="%(levelname)s %(message)s")
received = []

def handler(request):
    received.extend(json.loads(request.content)["data"])
    return httpx2.Response(200)

exporter = BackendSpanExporter(api_key="sk-fake")  # default OpenAI ingest endpoint
exporter._client = httpx2.Client(transport=httpx2.MockTransport(handler))
processor = BatchTraceProcessor(exporter, schedule_delay=3600)
set_trace_processors([processor])

async def main():
    agent = Agent(name="assistant", model=ScriptedModel([[assistant_message("hi")], [assistant_message("hi")]]))
    await Runner.run(agent, "hello", run_config=RunConfig(workflow_name="run_A"))  # nothing wrong with this run
    await Runner.run(agent, "hello", run_config=RunConfig(
        workflow_name="run_B", trace_metadata={"request_id": uuid.uuid4()}))

asyncio.run(main())
processor.force_flush()
print("items received:", len(received), [d.get("workflow_name") for d in received if d.get("object") == "trace"])

Expected behavior

run_A's trace and spans get exported no matter what's in run_B's metadata. At most, the value that can't be encoded gets dropped from run_B, and run_B's trace and spans still arrive.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions