Skip to content

fix(core): context preservation in shielded async callbacks#32163

Merged
Mason Daugherty (mdrxy) merged 10 commits into
masterfrom
copilot/fix-31398
Nov 7, 2025
Merged

fix(core): context preservation in shielded async callbacks#32163
Mason Daugherty (mdrxy) merged 10 commits into
masterfrom
copilot/fix-31398

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jul 22, 2025

The @shielded decorator in async callback managers was not preserving context variables, breaking OpenTelemetry instrumentation and other context-dependent functionality.

Problem

When using async callbacks with the @shielded decorator (applied to methods like on_llm_end, on_chain_end, etc.), context variables were not being preserved across the shield boundary. This caused issues with:

  • OpenTelemetry span context propagation
  • Other instrumentation that relies on context variables
  • Inconsistent context behavior between sync and async execution

The issue was reproducible with:

from contextvars import copy_context
import asyncio
from langgraph.graph import StateGraph

# Sync case: context remains consistent
print("SYNC")
print(copy_context())  # Same object
graph.invoke({"result": "init"})
print(copy_context())  # Same object

# Async case: context was inconsistent (before fix)
print("ASYNC") 
asyncio.run(graph.ainvoke({"result": "init"}))
print(copy_context())  # Different object than expected

Root Cause

The original shielded decorator implementation:

async def wrapped(*args: Any, **kwargs: Any) -> Any:
    return await asyncio.shield(func(*args, **kwargs))

Used asyncio.shield() directly without preserving the current execution context, causing context variables to be lost.

Solution

Modified the shielded decorator to:

  1. Capture the current context using copy_context()
  2. Create a task with explicit context using asyncio.create_task(coro, context=ctx) for Python 3.11+
  3. Shield the context-aware task
  4. Fallback to regular task creation for Python < 3.11
async def wrapped(*args: Any, **kwargs: Any) -> Any:
    # Capture the current context to preserve context variables
    ctx = copy_context()
    coro = func(*args, **kwargs)
    
    try:
        # Create a task with the captured context to preserve context variables
        task = asyncio.create_task(coro, context=ctx)
        return await asyncio.shield(task)
    except TypeError:
        # Python < 3.11 fallback
        task = asyncio.create_task(coro)
        return await asyncio.shield(task)

Testing

  • Added comprehensive test test_shielded_callback_context_preservation() that validates context variables are preserved across shielded callback boundaries
  • Verified the fix resolves the original LangGraph context consistency issue
  • Confirmed all existing callback manager tests still pass
  • Validated OpenTelemetry-like instrumentation scenarios work correctly

The fix is minimal, maintains backward compatibility, and ensures proper context preservation for both modern Python versions and older ones.

Fixes #31398.


💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click here to start the survey.

@vercel
Copy link
Copy Markdown

vercel Bot commented Jul 22, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
langchain ⬜️ Ignored Preview Aug 11, 2025 10:33pm

Co-authored-by: mdrxy <61371264+mdrxy@users.noreply.github.com>
Copilot AI changed the title [WIP] core/callbacks - context is not maintained when async callbacks get shielded core/callbacks - fix context preservation in shielded async callbacks Jul 22, 2025
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jul 22, 2025

CodSpeed Performance Report

Merging #32163 will not alter performance

Comparing copilot/fix-31398 (2174a80) with master (ca1a3fb)

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

Summary

✅ 13 untouched
⏩ 21 skipped1

Footnotes

  1. 21 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@mdrxy Mason Daugherty (mdrxy) changed the title core/callbacks - fix context preservation in shielded async callbacks fix(core): context preservation in shielded async callbacks Jul 22, 2025
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Jul 22, 2025

CodSpeed Instrumentation Performance Report

Merging #32163 will not alter performance

Comparing copilot/fix-31398 (8959773) with master (166c027)

Summary

✅ 14 untouched benchmarks

@mdrxy Mason Daugherty (mdrxy) marked this pull request as ready for review July 22, 2025 15:32
@mdrxy Mason Daugherty (mdrxy) added the core `langchain-core` package issues & PRs label Jul 28, 2025
@github-actions github-actions Bot added the fix For PRs that implement a fix label Oct 3, 2025
@mdrxy Mason Daugherty (mdrxy) merged commit d27211c into master Nov 7, 2025
92 checks passed
@mdrxy Mason Daugherty (mdrxy) deleted the copilot/fix-31398 branch November 7, 2025 18:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core `langchain-core` package issues & PRs fix For PRs that implement a fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

core(callbacks): context is not maintained when async callbacks get shielded

2 participants