#40881·langchain

`on_tool_error` does not receive the caller's `**kwargs` that `on_tool_start` and `on_tool_end` do

Author: Iris070308Created Sep 28, 2026Updated Sep 28, 2026
Labelsbugcoreexternal

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)

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)

bash
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