{"record":{"id":"55bb22d4c6c46129","repo":"langchain-ai/deepagents","slug":"tool-camel-not-registered","errorCode":null,"errorMessage":"tool '{camel}' not registered","messagePattern":"tool '(.+?)' not registered","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"libs/partners/quickjs/langchain_quickjs/_repl.py","lineNumber":710,"sourceCode":"        \"\"\"Install a host-function bridge for one camel-cased tool name.\n\n        The bridge is async so `eval_async`'s driving loop can await\n        `tool.ainvoke` without blocking the event loop. We look the\n        tool up through `self._registered_tools` on every call so a\n        later `install_tools` that swaps the underlying object (same\n        name, different instance) is picked up without re-registration.\n        \"\"\"\n        ctx = self._require_ctx()\n        registered = self._registered_tools\n\n        async def _bridge(raw_input: Any = None) -> Any:\n            tool = registered.get(camel)\n            if tool is None:\n                # Shouldn't happen — we only rewrite `globalThis.tools`\n                # with names currently in the map — but if a race causes\n                # it, fail loud.\n                msg = f\"tool '{camel}' not registered\"\n                raise RuntimeError(msg)\n            if self._ptc_state is None:\n                msg = \"PTC bridge called outside active eval\"\n                raise ConcurrentEvalError(msg)\n            state = self._ptc_state.consume_call_budget(\n                function_name=f\"tools.{camel}\",\n                max_ptc_calls=self._max_ptc_calls,\n            )\n            self._ptc_state = state\n            payload = _normalize_tool_input(raw_input)\n            call_id = _synth_tool_call_id(tool.name)\n            # Inject runtime/state/store ourselves; `InjectedToolCallId`\n            # is handled inside `_ainvoke_tool_on_outer_loop` via\n            # `tool.arun(..., tool_call_id=...)`. The bridge intentionally\n            # avoids the tool-call envelope path because it wraps the\n            # result in a `ToolMessage` and string-coerces `.content`,\n            # destroying native return types (lists, dicts, numbers).\n            args = _inject_tool_args_for_ptc(\n                tool, payload, state.outer_runtime, call_id","sourceCodeStart":692,"sourceCodeEnd":728,"githubUrl":"https://github.com/langchain-ai/deepagents/blob/a1af029e6e73cb17c36bff823d227747b28e91e1/libs/partners/quickjs/langchain_quickjs/_repl.py#L692-L728","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"# Python side: install tools only before evals start, with the full set\nrepl.install_tools(all_tools)  # never narrow the set mid-eval","typeGuard":"def tool_stable(repl, name: str) -> bool:\n    return name in repl._registered_tools","tryCatchPattern":"try:\n    const r = await tools.myTool(args);\n} catch (e) {\n  if (String(e).includes('not registered')) {\n    // tool set changed mid-eval; do not retry on same REPL\n    console.warn('tool removed during eval');\n  } else { throw e; }\n}","preventionTips":["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"],"tags":["concurrency","quickjs","tool-bridge","race-condition"],"backgroundTag":"tool-not-registered","analyzedSha":"a1af029e6e73cb17c36bff823d227747b28e91e1","analyzedAt":"2026-08-29T11:43:24.718Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}