Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
import tempfile
from pathlib import Path
from langchain_anthropic.middleware.anthropic_tools import (
StateClaudeTextEditorMiddleware,
StateClaudeMemoryMiddleware,
FilesystemClaudeTextEditorMiddleware,
FilesystemClaudeMemoryMiddleware,
)
# Site 1: state-backed text editor
mw = StateClaudeTextEditorMiddleware()
state = {mw.state_key: {"/some/old.txt": {
"content": "hi",
"created_at": "2026-01-01T00:00:00+00:00",
"modified_at": "2026-01-01T00:00:00+00:00",
}}}
try:
mw._handle_rename(
{"path": "/some/old.txt", "new_path": "/some/new.txt"}, state, "tc-1"
)
except KeyError as e:
print("state textedit:", repr(e))
# Site 2: state-backed memory (same base class)
mw = StateClaudeMemoryMiddleware()
state = {mw.state_key: {"/memories/old.txt": {
"content": "hi",
"created_at": "2026-01-01T00:00:00+00:00",
"modified_at": "2026-01-01T00:00:00+00:00",
}}}
try:
mw._handle_rename(
{"path": "/memories/old.txt", "new_path": "/memories/new.txt"},
state, "tc-2",
)
except KeyError as e:
print("state memory:", repr(e))
# Site 3: filesystem-backed text editor (sibling base class)
with tempfile.TemporaryDirectory() as root:
(Path(root) / "old.txt").write_text("hi")
mw = FilesystemClaudeTextEditorMiddleware(root_path=root)
try:
mw._handle_rename(
{"path": "/old.txt", "new_path": "/new.txt"}, "tc-3"
)
except KeyError as e:
print("fs textedit:", repr(e))
# Site 4: filesystem-backed memory
with tempfile.TemporaryDirectory() as root:
(Path(root) / "memories").mkdir()
(Path(root) / "memories" / "old.txt").write_text("hi")
mw = FilesystemClaudeMemoryMiddleware(root_path=root)
try:
mw._handle_rename(
{"path": "/memories/old.txt", "new_path": "/memories/new.txt"},
"tc-4",
)
except KeyError as e:
print("fs memory:", repr(e))
# Expected (and observed) output -- CONFIRMED BUG:
# state textedit: KeyError('old_path')
# state memory: KeyError('old_path')
# fs textedit: KeyError('old_path')
# fs memory: KeyError('old_path')
Error Message and Stack Trace (if applicable)
Traceback (most recent call last):
File "repro.py", line ..., in <module>
mw._handle_rename(
File ".../langchain_anthropic/middleware/anthropic_tools.py", line 558, in _handle_rename
old_path = args["old_path"]
~~~~^^^^^^^^^^^^
KeyError: 'old_path'
The KeyError fires on the first executable line of `_handle_rename`, before any state mutation or filesystem call. When this happens inside a LangGraph tool node the exception is not caught by the surrounding `except (ValueError, FileNotFoundError[, PermissionError]) as e:` block (lines [282](https://github.com/langchain-ai/langchain/blob/9d14a5e06d98355e5c0eccd0736b961fbe419f87/libs/partners/anthropic/langchain_anthropic/middleware/anthropic_tools.py#L282) and [774](https://github.com/langchain-ai/langchain/blob/9d14a5e06d98355e5c0eccd0736b961fbe419f87/libs/partners/anthropic/langchain_anthropic/middleware/anthropic_tools.py#L774)), so it propagates into the Pregel runner.
Description
The file_tool callback in _StateClaudeFileToolMiddleware.__init__ (anthropic_tools.py#L243-L253) builds an args dict containing path, file_text, old_str, new_str, insert_line, new_path, and view_range, then dispatches by command. The handler reached on the rename branch is _handle_rename (anthropic_tools.py#L554-L558), whose first executable line is old_path = args["old_path"] -- a key the producer never sets. The same producer/consumer pair is duplicated verbatim in _FilesystemClaudeFileToolMiddleware (producer at L745-L755, consumer at L1055-L1057), so all four user-facing subclasses (StateClaudeTextEditorMiddleware, StateClaudeMemoryMiddleware, FilesystemClaudeTextEditorMiddleware, FilesystemClaudeMemoryMiddleware) crash on every rename. This is graph-native because the args dict serves as a per-tool-call carrier between framework-side dispatch and a framework-internal handler; a direct Python call would never assemble the malformed dict in the first place. Expected: rename succeeds and the handler returns a Command updating state or filesystem. Actual: KeyError('old_path') escapes the tool node.
Suggested fix (smallest patch is producer-side; one line per producer):
# In file_tool(...) inside _StateClaudeFileToolMiddleware.__init__ (line ~252)
if new_path is not None:
args["new_path"] = new_path
+if command == "rename":
+ args["old_path"] = path
Apply the same one-liner to _FilesystemClaudeFileToolMiddleware.__init__ around line 754. Consider adding KeyError to the surrounding except (...) whitelist so future producer/consumer key drifts return as a string tool error instead of escaping.
System Info
OS: Linux
Submission checklist
Package (Required)
Related Issues / PRs
No response
Reproduction Steps / Example Code (Python)
Error Message and Stack Trace (if applicable)
Description
The
file_toolcallback in_StateClaudeFileToolMiddleware.__init__(anthropic_tools.py#L243-L253) builds anargsdict containingpath,file_text,old_str,new_str,insert_line,new_path, andview_range, then dispatches by command. The handler reached on therenamebranch is_handle_rename(anthropic_tools.py#L554-L558), whose first executable line isold_path = args["old_path"]-- a key the producer never sets. The same producer/consumer pair is duplicated verbatim in_FilesystemClaudeFileToolMiddleware(producer at L745-L755, consumer at L1055-L1057), so all four user-facing subclasses (StateClaudeTextEditorMiddleware,StateClaudeMemoryMiddleware,FilesystemClaudeTextEditorMiddleware,FilesystemClaudeMemoryMiddleware) crash on everyrename. This is graph-native because theargsdict serves as a per-tool-call carrier between framework-side dispatch and a framework-internal handler; a direct Python call would never assemble the malformed dict in the first place. Expected: rename succeeds and the handler returns aCommandupdating state or filesystem. Actual:KeyError('old_path')escapes the tool node.Suggested fix (smallest patch is producer-side; one line per producer):
Apply the same one-liner to
_FilesystemClaudeFileToolMiddleware.__init__around line 754. Consider addingKeyErrorto the surroundingexcept (...)whitelist so future producer/consumer key drifts return as a string tool error instead of escaping.System Info