fix(langchain): detach orphaned context tokens in _create_llm_span#3974
fix(langchain): detach orphaned context tokens in _create_llm_span#3974RRocaP wants to merge 1 commit intotraceloop:mainfrom
Conversation
When _create_llm_span() is called for a run_id that already has a SpanHolder, the existing entry is silently overwritten. The old holder's context token (and association_properties_token) are lost without being detached, which corrupts the OpenTelemetry context stack. This adds a check before the overwrite: if an existing SpanHolder is found, its tokens are safely detached before being replaced. Follows the same pattern used in _end_span() and aligns with the fixes in traceloop#3526 and traceloop#3807. Fixes traceloop#3957
|
|
📝 WalkthroughWalkthroughThe callback handler in the LangChain instrumentation now safely detaches orphaned context tokens before overwriting an existing Changes
Estimated Code Review Effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly Related Issues
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py`:
- Around line 475-482: The current code detaches tokens from self.spans[run_id]
after _create_span() has already overwritten that entry, which can detach the
new span's token instead of the prior holder's; modify the logic in the method
where _create_span(run_id, ...) is called so that you first retrieve existing =
self.spans.get(run_id) and call _safe_detach_context(existing.token) and
_safe_detach_context(existing.association_properties_token) (guarded by getattr)
before invoking _create_span and assigning to self.spans[run_id]; ensure you
keep the existing variable names (existing, run_id) and the helper
_safe_detach_context to locate and fix the spot.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c2d8ebbe-64d8-44ae-aac8-2cde82488a5b
📒 Files selected for processing (1)
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py
| # Detach orphaned context tokens before overwriting (see #3957). | ||
| existing = self.spans.get(run_id) | ||
| if existing is not None: | ||
| if existing.token: | ||
| self._safe_detach_context(existing.token) | ||
| if getattr(existing, "association_properties_token", None): | ||
| self._safe_detach_context(existing.association_properties_token) | ||
|
|
There was a problem hiding this comment.
Detach order is wrong; reused run_id tokens can still leak.
At Line 476, existing = self.spans.get(run_id) happens after _create_span() (Line 441) has already replaced self.spans[run_id]. This detaches the current holder’s token, not the prior holder’s tokens, so the reuse path can still leave stale context attached.
Proposed fix
def _create_llm_span(
self,
run_id: UUID,
@@
) -> Span:
+ # Detach previous holder tokens before _create_span overwrites run_id.
+ previous = self.spans.get(run_id)
+ if previous is not None:
+ if previous.token:
+ self._safe_detach_context(previous.token)
+ if getattr(previous, "association_properties_token", None):
+ self._safe_detach_context(previous.association_properties_token)
+
workflow_name = self.get_workflow_name(parent_run_id)
entity_path = self.get_entity_path(parent_run_id)
@@
- existing = self.spans.get(run_id)
- if existing is not None:
- if existing.token:
- self._safe_detach_context(existing.token)
- if getattr(existing, "association_properties_token", None):
- self._safe_detach_context(existing.association_properties_token)
+ # _create_span stored current span-context token; detach before replacing holder.
+ current = self.spans.get(run_id)
+ if current is not None and current.token:
+ self._safe_detach_context(current.token)
+ if current is not None and getattr(current, "association_properties_token", None):
+ self._safe_detach_context(current.association_properties_token)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.py`
around lines 475 - 482, The current code detaches tokens from self.spans[run_id]
after _create_span() has already overwritten that entry, which can detach the
new span's token instead of the prior holder's; modify the logic in the method
where _create_span(run_id, ...) is called so that you first retrieve existing =
self.spans.get(run_id) and call _safe_detach_context(existing.token) and
_safe_detach_context(existing.association_properties_token) (guarded by getattr)
before invoking _create_span and assigning to self.spans[run_id]; ensure you
keep the existing variable names (existing, run_id) and the helper
_safe_detach_context to locate and fix the spot.
Summary
Fixes #3957
Problem
_create_llm_span()overwrites an existingSpanHolderentrywithout detaching the old holder's context tokens (
tokenandassociation_properties_token). This silently corrupts theOpenTelemetry context stack, causing downstream spans to inherit
incorrect or stale context.
Related to fixes in #3526 and #3807 which addressed similar
orphaned
context_api.attach()calls in other code paths.Fix
Before replacing a
SpanHolderentry, check if one already existsfor the given
run_id. If so, safely detach its context tokensusing the existing
_safe_detach_context()helper.Files changed
packages/opentelemetry-instrumentation-langchain/opentelemetry/instrumentation/langchain/callback_handler.pyTest plan
Summary by CodeRabbit