langchain-ai/deepagents · error · RuntimeError
tool '{camel}' not registered
Error message
tool '{camel}' not registered What it means
The tool bridge raises this RuntimeError when a host-function bridge for a camelCase tool name is invoked but the name is no longer present in `_registered_tools`. Per the source comment this 'shouldn't happen' — `globalThis.tools` is only rewritten with currently-registered names — so the library fails loudly rather than silently swallowing it, suggesting a race between bridge registration and tool-map updates.
Source
Thrown at libs/partners/quickjs/langchain_quickjs/_repl.py:710
"""Install a host-function bridge for one camel-cased tool name.
The bridge is async so `eval_async`'s driving loop can await
`tool.ainvoke` without blocking the event loop. We look the
tool up through `self._registered_tools` on every call so a
later `install_tools` that swaps the underlying object (same
name, different instance) is picked up without re-registration.
"""
ctx = self._require_ctx()
registered = self._registered_tools
async def _bridge(raw_input: Any = None) -> Any:
tool = registered.get(camel)
if tool is None:
# Shouldn't happen — we only rewrite `globalThis.tools`
# with names currently in the map — but if a race causes
# it, fail loud.
msg = f"tool '{camel}' not registered"
raise RuntimeError(msg)
if self._ptc_state is None:
msg = "PTC bridge called outside active eval"
raise ConcurrentEvalError(msg)
state = self._ptc_state.consume_call_budget(
function_name=f"tools.{camel}",
max_ptc_calls=self._max_ptc_calls,
)
self._ptc_state = state
payload = _normalize_tool_input(raw_input)
call_id = _synth_tool_call_id(tool.name)
# Inject runtime/state/store ourselves; `InjectedToolCallId`
# is handled inside `_ainvoke_tool_on_outer_loop` via
# `tool.arun(..., tool_call_id=...)`. The bridge intentionally
# avoids the tool-call envelope path because it wraps the
# result in a `ToolMessage` and string-coerces `.content`,
# destroying native return types (lists, dicts, numbers).
args = _inject_tool_args_for_ptc(
tool, payload, state.outer_runtime, call_idView on GitHub (pinned to a1af029e6e)
Solutions
- Don't mutate the tool set (call `install_tools`) while an eval using `tools.*` is in flight
- Always pass the full current tool list to `install_tools` so names never disappear mid-run
- Use one REPL per concurrent evaluation to avoid shared-state races
Defensive patterns
Strategy: try-catch
Validate before calling
# Python side: install tools only before evals start, with the full set repl.install_tools(all_tools) # never narrow the set mid-eval
Type guard
def tool_stable(repl, name: str) -> bool:
return name in repl._registered_tools Try / catch
try:
const r = await tools.myTool(args);
} catch (e) {
if (String(e).includes('not registered')) {
// tool set changed mid-eval; do not retry on same REPL
console.warn('tool removed during eval');
} else { throw e; }
} Prevention
- Never call install_tools while an eval is in flight
- Always pass the complete current tool list to install_tools
- Isolate REPLs per thread/eval to prevent shared-state races
When it happens
Trigger: JS calls `tools.someTool(...)` where `someTool` exists on `globalThis.tools` but was removed/rotated out of `_registered_tools` concurrently — e.g. `install_tools` ran with a narrowed toolset while an in-flight eval still holds a reference to the old bridge.
Common situations: Calling `install_tools` with a reduced tool set while an eval is mid-flight, REPL shared across threads with racing tool installs, or stale JS cached in the context referencing removed tools.
Related errors
- task bridge called outside active eval
- PTC bridge called outside active eval
- temporary artifact identity changed
- workspace binding was not persisted for thread {thread_id}
- Context Hub mutation deadline is missing
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/55bb22d4c6c46129.
Report an issue: GitHub.