Skip to content

ValueError: ContextVar token "was created in a different Context" when using concurrent Runner.run() calls with asyncio.gather #2246

@atpug22

Description

@atpug22

Summary

When running multiple Runner.run() calls concurrently using asyncio.gather() (common in batch processing scenarios), Python raises a ValueError related to ContextVar token management. This is similar to the issue fixed in #538 for run_streamed(), but affects the regular synchronous Runner.run() pattern.

Environment

  • openai-agents version: 0.6.x (also affects forked version with Gemini support)
  • Python version: 3.11+
  • OS: Linux (production), macOS (development)

Error Message

ValueError: <Token var=<ContextVar name='current_trace' default=None at 0x7f7a7f7279c0> at 0x7f7a73ea0200> was created in a different Context

Stack Trace Location

File "agents/tracing/scope.py", line 49, in reset_current_trace
    _current_trace.reset(token)

Reproduction

import asyncio
from agents import Agent, Runner

async def process_batch(inputs: list[str]):
    """Process multiple inputs concurrently - triggers ContextVar error"""

    async def process_single(input_text: str):
        agent = Agent(
            name="processor",
            instructions="Process the input",
            model="gpt-4.1-mini",
        )
        result = await Runner.run(agent, input_text)
        return result.final_output

    # Concurrent execution triggers the error
    tasks = [process_single(inp) for inp in inputs]
    results = await asyncio.gather(*tasks)
    return results

# This will fail with ContextVar error when inputs > 1
asyncio.run(process_batch(["input1", "input2", "input3"]))

Root Cause Analysis

The issue is in agents/tracing/traces.py and agents/tracing/scope.py:

  1. When Runner.run() starts, it calls trace.start(mark_as_current=True) which does:

    self._prev_context_token = Scope.set_current_trace(self)
  2. When Runner.run() finishes, it calls trace.finish(reset_current=True) which does:

    Scope.reset_current_trace(self._prev_context_token)
  3. With concurrent asyncio.gather() tasks:

    • Task 1: set_current_trace() → gets token A
    • Task 2: set_current_trace() → gets token B
    • Task 3: set_current_trace() → gets token C
    • Task 1 finishes: reset_current_trace(token A) → changes ContextVar
    • Task 3 finishes: reset_current_trace(token C)ERROR: token C was created when ContextVar had different state

Relationship to #538

Issue #538 and PR #540 fixed this for run_streamed() by ensuring trace start/finish happen in the same async context. However, the same pattern occurs with concurrent Runner.run() calls, which wasn't addressed.

Current Workaround

We're using RunConfig(tracing_disabled=True) for batch operations:

from agents import RunConfig

batch_config = RunConfig(tracing_disabled=True)

async def process_single(input_text: str):
    agent = Agent(...)
    result = await Runner.run(agent, input_text, run_config=batch_config)
    return result.final_output

This works but loses all tracing data for batch operations.

Proposed Solutions

  1. Apply same fix as Start and finish streaming trace in impl metod #540 - Ensure trace lifecycle is managed within each task's context
  2. Use contextvars.copy_context() - Give each concurrent task its own context copy
  3. Catch and handle the ValueError - Gracefully handle the reset failure without crashing

Impact

  • Frequency: 19 errors in production from a single batch operation
  • Affected use case: Any batch processing that uses concurrent Runner.run() calls
  • Workaround available: Yes, but loses tracing capability

Additional Context

Production error pattern (all 19 errors within 1 second, same batch):

2025-12-29 17:48:19.219 | generate_content | ContextVar error
2025-12-29 17:48:19.223 | generate_content | ContextVar error
2025-12-29 17:48:19.227 | generate_content | ContextVar error
... (16 more within same second)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions