Skip to content

fix(langchain): preserve inherited callback handlers - #1029

Open
morgan-coded wants to merge 1 commit into
traceloop:mainfrom
morgan-coded:fix/langchain-callback-manager-1016
Open

fix(langchain): preserve inherited callback handlers#1029
morgan-coded wants to merge 1 commit into
traceloop:mainfrom
morgan-coded:fix/langchain-callback-manager-1016

Conversation

@morgan-coded

@morgan-coded morgan-coded commented Jul 7, 2026

Copy link
Copy Markdown

The patched _configureSync in patchCallbackManager assumed inheritableHandlers was always an array, so it discarded any CallbackManager instance passed in and dropped inherited handlers such as RootListenersTracer, breaking RunnableWithMessageHistory chat history persistence. It also added a new TraceloopCallbackHandler unconditionally, duplicating it on child runnable invocations and doubling emitted spans. This normalizes inheritableHandlers by extracting its handler list whether it is an array or a CallbackManager instance, and filters out any existing traceloop_callback_handler before adding the current one. Added a focused test covering both cases — an array input with an existing Traceloop handler, and a CallbackManager instance input with a sentinel handler — both of which fail on the pre-fix code and pass after the fix. Verified with the package's test suite, lint, and build.

Fixes #1016

Summary by CodeRabbit

  • Bug Fixes

    • Improved handler patching so callback configuration works correctly even when inheritable handlers are provided in different shapes.
    • Prevented duplicate tracing handlers from being added during child runnable setup, while preserving existing user-defined handlers.
  • Tests

    • Added coverage for callback configuration behavior across multiple input formats, including de-duplication checks.

The patched CallbackManager._configureSync assumed inheritableHandlers
was always an array, silently dropping handlers when LangChain passed
a CallbackManager instance instead (e.g. via runManager.getChild()).
It also injected a new TraceloopCallbackHandler unconditionally,
duplicating it on child runnable invocations.

Normalize inheritableHandlers by extracting the handler list whether
it is an array or a CallbackManager instance, and filter out any
existing traceloop_callback_handler before adding the current one.
Copilot AI review requested due to automatic review settings July 7, 2026 21:39
@CLAassistant

CLAassistant commented Jul 7, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The patch for LangChain's CallbackManager._configureSync now accepts non-array inheritableHandlers inputs by extracting handlers from CallbackManager-like objects, and deduplicates the Traceloop callback handler by filtering existing handlers with matching names before appending. New unit tests validate both scenarios.

Changes

Callback handler patch fix

Layer / File(s) Summary
Handle non-array inheritableHandlers and dedupe TraceloopCallbackHandler
packages/instrumentation-langchain/src/instrumentation.ts
Broadens the inheritableHandlers parameter type, extracts existing handlers from arrays or CallbackManager-like objects, and filters out same-named handlers before appending the new TraceloopCallbackHandler to prevent duplication and handler loss.
Tests validating handler preservation and deduplication
packages/instrumentation-langchain/test/callback-manager-handlers.test.ts
Adds a FakeCallbackManager test fixture and two test cases confirming sentinel handlers are preserved and traceloop_callback_handler appears exactly once for both array and non-array inheritableHandlers inputs.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant RunnableWithMessageHistory
  participant CallbackManager
  participant PatchedConfigureSync
  participant OriginalConfigureSync

  RunnableWithMessageHistory->>CallbackManager: getChild()
  CallbackManager->>PatchedConfigureSync: _configureSync(inheritableHandlers, localHandlers)
  PatchedConfigureSync->>PatchedConfigureSync: extract existing handlers (array or object)
  PatchedConfigureSync->>PatchedConfigureSync: filter out duplicate traceloop_callback_handler
  PatchedConfigureSync->>PatchedConfigureSync: append TraceloopCallbackHandler
  PatchedConfigureSync->>OriginalConfigureSync: call(updatedHandlers, localHandlers)
  OriginalConfigureSync-->>RunnableWithMessageHistory: configured CallbackManager with preserved handlers
Loading

Related issues: #1016 (bug describing dropped handlers and duplicated TraceloopCallbackHandler in _configureSync)

Suggested labels: bug, instrumentation-langchain

Suggested reviewers: nirga

Poem:
A rabbit peeked at handlers spread,
Some in arrays, some instead
Tucked inside a manager's frame—
No more dropping, no more shame.
One clean trace, no doubled name! 🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main LangChain callback-handling fix.
Linked Issues check ✅ Passed The changes preserve non-array inheritable handlers and deduplicate TraceloopCallbackHandler as requested in #1016.
Out of Scope Changes check ✅ Passed The PR stays focused on the reported LangChain instrumentation bug and its targeted test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/instrumentation-langchain/test/callback-manager-handlers.test.ts (1)

43-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Shared static patch state defeats per-test isolation.

FakeCallbackManager is a single module-level class. Once beforeEach in the first test patches _configureSync and sets _traceloopPatched, the second beforeEach() call to manuallyInstrument becomes a no-op (patchCallbackManager skips if _traceloopPatched is already set), so the second test actually exercises the closure/self captured by the first test's instrumentation instance, not the fresh one created in its own beforeEach. The assertions still pass here because the tested logic is stateless w.r.t. self, but this masks the fact that per-test setup doesn't actually re-patch, and could hide bugs in future tests that depend on instance-specific state (e.g., tracer/config differences).

Consider resetting _traceloopPatched (or defining a fresh FakeCallbackManager class) per test to ensure genuine isolation.

♻️ Proposed fix for test isolation
   beforeEach(() => {
     const provider = new NodeTracerProvider();
     instrumentation = new LangChainInstrumentation();
     instrumentation.setTracerProvider(provider);
+    delete (FakeCallbackManager as unknown as Record<string, unknown>)
+      ._traceloopPatched;
     instrumentation.manuallyInstrument({
       callbackManagerModule: { CallbackManager: FakeCallbackManager },
     });
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/instrumentation-langchain/test/callback-manager-handlers.test.ts`
around lines 43 - 100, The test setup for FakeCallbackManager is sharing patched
static state across cases, so the second beforeEach does not truly re-run
manuallyInstrument and may reuse the first LangChainInstrumentation instance’s
patched _configureSync closure. Update the test isolation around
patchCallbackManager/_traceloopPatched by resetting that static flag or creating
a fresh FakeCallbackManager per test, so each beforeEach gets a clean patch and
the assertions exercise the current instrumentation instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/instrumentation-langchain/test/callback-manager-handlers.test.ts`:
- Around line 43-100: The test setup for FakeCallbackManager is sharing patched
static state across cases, so the second beforeEach does not truly re-run
manuallyInstrument and may reuse the first LangChainInstrumentation instance’s
patched _configureSync closure. Update the test isolation around
patchCallbackManager/_traceloopPatched by resetting that static flag or creating
a fresh FakeCallbackManager per test, so each beforeEach gets a clean patch and
the assertions exercise the current instrumentation instance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7e98061-5714-4ff1-8df1-51017e6f3d61

📥 Commits

Reviewing files that changed from the base of the PR and between 1faf69d and 9ebfdb3.

📒 Files selected for processing (2)
  • packages/instrumentation-langchain/src/instrumentation.ts
  • packages/instrumentation-langchain/test/callback-manager-handlers.test.ts

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] [instrumentation-langchain] _configureSync patch drops non-array inheritableHandlers and duplicates TraceloopCallbackHandler

3 participants