Skip to content

fix(core): merge callbacks without re-registering handlers both configs already hold - #2696

Open
Serhiy Bzhezytskyy (serhiy-bzhezytskyy) wants to merge 1 commit into
langchain-ai:mainfrom
serhiy-bzhezytskyy:fix/dedupe-merged-callback-handlers
Open

fix(core): merge callbacks without re-registering handlers both configs already hold#2696
Serhiy Bzhezytskyy (serhiy-bzhezytskyy) wants to merge 1 commit into
langchain-ai:mainfrom
serhiy-bzhezytskyy:fix/dedupe-merged-callback-handlers

Conversation

@serhiy-bzhezytskyy

Copy link
Copy Markdown

Summary

Fixes the duplication half of #2570.

With a LangChainTracer attached, a graph containing a subgraph emits every on_chat_model_stream event once per nesting level, so messages/partial consumers see multiplied content and unparseable tool_call args with no provider involvement. The same duplication multiplies LangSmith ingestion and produces the reported handleChainEnd: Run ID … not found in run map errors.

Root cause

At every nested-runnable boundary ensureLangGraphConfig merges the ambient AsyncLocalStorage config with the child config, and both carry the same handler instances; mergeCallbacks concatenates without checking, so the handlers compound per level (1 → 3 → 6). The Python runtime does not have the defect — its add_handler checks membership — and pure langchain-core with the same nesting does not duplicate either, so the fix belongs to this merge rather than to core's addHandler.

Changes

  • pregel/utils/config.tsmergeCallbacks adds a handler from the other side only when it is not already present. Distinct instances merge as before.

Test plan

  • Six new tests in pregel/utils/config.test.ts: the four merge shapes plus the ambient path, all red on main, and a guard that two distinct instances sharing a name still both register
  • Before/after: on_chat_model_stream 6 → 2 per two chunks (12 → 2 three levels deep); .stream(streamMode: "messages") 6 → 2; LangSmith createRun posts 12 → 8 for 7 runs; run-map errors 5 → 0; no-tracer behavior unchanged; no measurable wall-time cost
  • Changeset; langgraph-core suite 1,644 passed, typecheck clean

Related

…gs already hold

At a nested-runnable boundary ensureLangGraphConfig merges the ambient
AsyncLocalStorage config with the child config, and both carry the same
handler instances; concatenating them re-registers every handler once
per boundary, compounding per level. With a LangChainTracer attached,
every on_chat_model_stream event fires three times two subgraph levels
deep (langchain-ai#2570), LangSmith ingestion multiplies, and EventStreamCallbackHandler
reports 'Run ID not found in run map'. The Python runtime does not
duplicate: its add_handler checks membership.

mergeCallbacks now adds a handler from the other side only when it is
not already present; distinct instances merge as before. Red-first
tests cover all four merge shapes plus the ambient path, and a guard
pins the dedupe to instance identity.
@changeset-bot

changeset-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0fa183e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@langchain/langgraph Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Aug 14, 2026

Copy link
Copy Markdown

Open in StackBlitz

@langchain/langgraph-checkpoint

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint@2696

@langchain/langgraph-checkpoint-mongodb

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-mongodb@2696

@langchain/langgraph-checkpoint-postgres

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-postgres@2696

@langchain/langgraph-checkpoint-redis

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-redis@2696

@langchain/langgraph-checkpoint-sqlite

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-sqlite@2696

@langchain/langgraph-checkpoint-validation

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-checkpoint-validation@2696

create-langgraph

npm i https://pkg.pr.new/langchain-ai/langgraphjs/create-langgraph@2696

@langchain/langgraph-api

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-api@2696

@langchain/langgraph-cli

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-cli@2696

@langchain/langgraph

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph@2696

@langchain/langgraph-cua

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-cua@2696

@langchain/langgraph-supervisor

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-supervisor@2696

@langchain/langgraph-swarm

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-swarm@2696

@langchain/langgraph-ui

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-ui@2696

@langchain/langgraph-sdk

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/langgraph-sdk@2696

@langchain/angular

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/angular@2696

@langchain/react

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/react@2696

@langchain/svelte

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/svelte@2696

@langchain/vue

npm i https://pkg.pr.new/langchain-ai/langgraphjs/@langchain/vue@2696

commit: 0fa183e

@serhiy-bzhezytskyy

Copy link
Copy Markdown
Author

Review context from this file's history: #2531 fixed the bound + invoke-time pair of this same defect (the "no double-firing" guard in tracing.test.ts), and #2315 is the file's most recent tracer-related change — this PR closes the remaining pair, the ambient AsyncLocalStorage config + the child config. If a single property test over every merge pair would be a useful guard against a third variant, I can add it here or as a follow-up. Hope I'm bringing value here, not noise.

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.

1 participant