`on_tool_error` does not receive the caller's `**kwargs` that `on_tool_start` and `on_tool_end` do
Submission checklist
- This is a bug, not a usage question.
- I added a clear and descriptive title that summarizes this issue.
- I used the GitHub search to find a similar question and didn't find it.
- I am sure that this is a bug in LangChain rather than my code.
- The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
- This is not related to the langchain-community package.
- I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.
Package (Required)
- langchain
- langchain-openai
- langchain-anthropic
- langchain-classic
- langchain-core
- langchain-model-profiles
- langchain-tests
- langchain-text-splitters
- langchain-chroma
- langchain-deepseek
- langchain-exa
- langchain-fireworks
- langchain-groq
- langchain-huggingface
- langchain-mistralai
- langchain-nomic
- langchain-ollama
- langchain-openrouter
- langchain-perplexity
- langchain-qdrant
- langchain-typesafe
- langchain-xai
- Other / not sure / general
Related Issues / PRs
Repro: on_tool_error does not get the **kwargs that on_tool_start and on_tool_end do.
BaseTool.run's docstring says the keyword arguments go to the tool callbacks. Exit code 1 means one of the callback events lost them.
Reproduction Steps / Example Code (Python)
import asyncio
from typing import Any
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.tools import StructuredTool, ToolException
class Capture(BaseCallbackHandler):
def __init__(self):
self.events = {}
def on_tool_start(self, serialized, input_str, **kwargs: Any):
self.events["start"] = kwargs
def on_tool_end(self, output, **kwargs: Any):
self.events["end"] = kwargs
def on_tool_error(self, error, **kwargs: Any):
self.events["error"] = kwargs
def boom(x: int) -> str:
"""Fail on purpose."""
raise ToolException("boom")
def fine(x: int) -> str:
"""Succeed."""
return "ok"
fail_tool = StructuredTool.from_function(boom)
ok_tool = StructuredTool.from_function(fine)
def probe(fail):
handler = Capture()
tool = fail_tool if fail else ok_tool
try:
tool.invoke({"x": 1}, {"callbacks": [handler]}, tenant="acme")
except Exception:
pass
return handler.events
async def aprobe(fail):
handler = Capture()
tool = fail_tool if fail else ok_tool
try:
await tool.ainvoke({"x": 1}, {"callbacks": [handler]}, tenant="acme")
except Exception:
pass
return handler.events
def report(label, failing, succeeding):
got = {
"on_tool_start": "tenant" in failing.get("start", {}),
"on_tool_end": "tenant" in succeeding.get("end", {}),
"on_tool_error": "tenant" in failing.get("error", {}),
}
missing = [name for name, ok in got.items() if not ok]
print(f" {label:6s} {got}{' <-- lost: ' + str(missing) if missing else ''}")
return missing
missing = report("sync", probe(fail=True), probe(fail=False))
missing += report("async", asyncio.run(aprobe(fail=True)), asyncio.run(aprobe(fail=False)))
print(f"\nFAIL: these events lost the caller's kwargs: {missing}" if missing else "\nOK: every event received them")
raise SystemExit(1 if missing else 0)Error Message and Stack Trace (if applicable)
sync {'on_tool_start': True, 'on_tool_end': True, 'on_tool_error': False} <-- lost: ['on_tool_error']
async {'on_tool_start': True, 'on_tool_end': True, 'on_tool_error': False} <-- lost: ['on_tool_error']
FAIL: these events lost the caller's kwargs: ['on_tool_error', 'on_tool_error']Description
on_tool_error does not get the **kwargs I pass to the tool, even though on_tool_start and on_tool_end do. BaseTool.run's docstring says the keyword arguments go to the tool callbacks. In libs/core/langchain_core/tools/base.py lines 1134 and 1265 call on_tool_end(..., **kwargs), but lines 1131 and 1261 call on_tool_error(error_to_raise, tool_call_id=tool_call_id) with no kwargs.
So if a handler keys off something you pass in -- a tenant id, a trace tag, whatever -- it sees it on success and not on failure. That is the moment you want it most, and it is the kind of gap you only notice after the fact.
Expanding the kwargs into the on_tool_error call the same way the neighbouring events do should be enough. There is nothing else relying on the current call shape.
System Info
langchain-core: 1.6.5 langchain: 1.4.2 langgraph: 1.2.11 pydantic: 2.12.5 Python: 3.11.15 upstream commit tested: 1ef23d6b7f6d83508a8cb531feeb88860e4dec40 OS: Windows 10 + WSL No API key or network access is needed for the reproduction.
Social handles (optional)
No response
Source: langchain-ai/langchain