You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The tool dispatch path in tools/registry.py::ToolRegistry.dispatch() rejects tool calls whose name field has cosmetic variations that are trivially resolvable, producing spurious Unknown tool: X errors and forcing the model into retry loops.
Observed variations from Claude 4.5 / 4.6 / 4.7:
Emitted name
Canonical target
TodoTool_tool
todo
Todo_tool
todo
Patch_tool
patch
BrowserClick_tool
browser_click
Terminal_tool
terminal
WriteFile_tool
write_file
The drift pattern is CamelCase + trailing _tool suffix + mixed case. These are never ambiguous with any real tool name — all registered tools use lowercase snake_case.
anthropic/anthropic-cookbook#50235 — upstream advisory recommending normalization at the dispatch boundary.
Observed repeatedly in production gateway logs on this installation (openclaw, PID 2473729), especially after a prior dispatch error — the retry often worsens the drift.
Root cause
dispatch() does an exact-match lookup against the registry. A miss returns {"error": "Unknown tool: X"} immediately, with no attempt to normalize.
Proposed fix
Add a _normalize_tool_name() helper and call it as a fallback (only on miss — exact match still wins). Layered drift like TodoTool_tool needs fixpoint iteration:
Bounded MAX_ITER = 4. Logs a warning when normalization recovers a call so drift stays visible.
Safety properties:
Zero impact on well-formed calls — fallback is never reached when exact match succeeds.
No false matches — normalization produces lowercase snake_case; collisions with real tool names would already have been exact matches.
Truly unknown tools still error — e.g. NotARealTool_tool → Unknown tool: NotARealTool_tool (verified).
PR
Will open from JayGwod/hermes-agent:fix/registry-tool-name-normalize.
Live-verified after gateway reload: all 5 drift patterns above route to the correct handler; a control case (NotARealTool_tool) still returns the expected error.
Summary
The tool dispatch path in
tools/registry.py::ToolRegistry.dispatch()rejects tool calls whosenamefield has cosmetic variations that are trivially resolvable, producing spuriousUnknown tool: Xerrors and forcing the model into retry loops.Observed variations from Claude 4.5 / 4.6 / 4.7:
TodoTool_tooltodoTodo_tooltodoPatch_toolpatchBrowserClick_toolbrowser_clickTerminal_toolterminalWriteFile_toolwrite_fileThe drift pattern is CamelCase + trailing
_toolsuffix + mixed case. These are never ambiguous with any real tool name — all registered tools use lowercase snake_case.Evidence of prevalence
claude-sonnet-4-5model mastra-ai/mastra#12581 — empirical 44-model study: Claude family fails tool-name conformance at ~30%, other families ≈ 0%.Root cause
dispatch()does an exact-match lookup against the registry. A miss returns{"error": "Unknown tool: X"}immediately, with no attempt to normalize.Proposed fix
Add a
_normalize_tool_name()helper and call it as a fallback (only on miss — exact match still wins). Layered drift likeTodoTool_toolneeds fixpoint iteration:Bounded
MAX_ITER = 4. Logs awarningwhen normalization recovers a call so drift stays visible.Safety properties:
NotARealTool_tool→Unknown tool: NotARealTool_tool(verified).PR
Will open from
JayGwod/hermes-agent:fix/registry-tool-name-normalize.Live-verified after gateway reload: all 5 drift patterns above route to the correct handler; a control case (
NotARealTool_tool) still returns the expected error.