{"record":{"id":"e4a14be53d09b0b7","repo":"ZhuLinsen/daily_stock_analysis","slug":"tool-not-found","errorCode":"tool_not_found","errorMessage":"DSA tool is not registered: {name}","messagePattern":"DSA tool is not registered: (.+?)","errorType":"error_code","errorClass":"CodexAppServerError","httpStatus":null,"severity":"error","filePath":"src/agent/codex_app_server_transport.py","lineNumber":159,"sourceCode":"    if isinstance(reasoning_tokens, int) and not isinstance(reasoning_tokens, bool) and reasoning_tokens >= 0:\n        usage[\"completion_tokens_details\"] = {\"reasoning_tokens\": reasoning_tokens}\n    return usage if \"total_tokens\" in usage else None\n\n\ndef controlled_environment(source: Optional[Dict[str, str]] = None) -> Dict[str, str]:\n    \"\"\"Return the allowlisted environment inherited by Codex.\"\"\"\n    environment = os.environ if source is None else source\n    return {name: environment[name] for name in _ALLOWED_ENV_NAMES if environment.get(name)}\n\n\ndef dynamic_tool_specs(surface: ToolSurface, names: Iterable[str]) -> list[dict]:\n    \"\"\"Convert ToolSurface MCP descriptors into App Server dynamic tools.\"\"\"\n    descriptors = {item[\"name\"]: item for item in surface.list_tools(\"mcp_descriptor\")}\n    specs = []\n    for name in names:\n        descriptor = descriptors.get(name)\n        if descriptor is None:\n            raise CodexAppServerError(\"tool_not_found\", f\"DSA tool is not registered: {name}\")\n        specs.append(\n            {\n                \"type\": \"function\",\n                \"name\": descriptor[\"name\"],\n                \"description\": descriptor[\"description\"],\n                \"inputSchema\": descriptor[\"inputSchema\"],\n            }\n        )\n    return specs\n\n\nclass CodexAppServerTransport:\n    \"\"\"Bidirectional JSONL client for one ephemeral App Server process.\"\"\"\n\n    def __init__(\n        self,\n        command: Sequence[str],\n        *,","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/agent/codex_app_server_transport.py#L141-L177","documentation":"dynamic_tool_specs() converts ToolSurface MCP descriptors into App Server dynamic-tool specs. It builds a name->descriptor map from surface.list_tools('mcp_descriptor') and looks up each requested tool name; a miss means the name was never registered on the ToolSurface, so it raises code 'tool_not_found'. This is a caller-side consistency error: the thread's tool_names list and the registry disagree.","triggerScenarios":"Passing a tool name to start_thread/run_turn that is not registered in the ToolRegistry backing the ToolSurface; renaming or removing a tool from the registry while callers still request the old name; typos or case mismatch in the tool name string; a registry populated in a different process than the one serving the request.","commonSituations":"A tool is renamed during a refactor but the Codex backend's allowlist constant is not updated; tests construct a partial registry and request the full tool list; distributed setups where tool registration happens lazily and has not completed when the thread starts.","solutions":["Diff the requested tool name list against surface.list_tools('mcp_descriptor') names before calling start_thread and fix or drop the unknown names","If the tool should exist, register it in the ToolRegistry that the ToolSurface was constructed with","Update stale references after a tool rename, keeping the registry the single source of truth","Add a unit test asserting the backend's allowlisted tool names are a subset of registered MCP descriptors"],"exampleFix":"# before\nnames = [\"stock_analysis\", \"typo_tool\"]\nthread = client.start_thread(tool_names=names, ...)\n\n# after\nregistered = {d[\"name\"] for d in surface.list_tools(\"mcp_descriptor\")}\nunknown = [n for n in names if n not in registered]\nif unknown:\n    raise ValueError(f\"unregistered tools requested: {unknown}\")\nthread = client.start_thread(tool_names=names, ...)","handlingStrategy":"validation","validationCode":"registered = {d[\"name\"] for d in surface.list_tools(\"mcp_descriptor\")}\nunknown = [n for n in requested_tool_names if n not in registered]\nif unknown:\n    raise ValueError(f\"tool names not registered on ToolSurface: {unknown}\")","typeGuard":"def all_tools_registered(surface: ToolSurface, names: list[str]) -> bool:\n    registered = {d[\"name\"] for d in surface.list_tools(\"mcp_descriptor\")}\n    return set(names) <= registered","tryCatchPattern":"try:\n    client.start_thread(tool_names=names, ...)\nexcept CodexAppServerError as exc:\n    if exc.code == \"tool_not_found\":\n        names = prune_to_registered(surface, names)  # or fail loudly\n    raise","preventionTips":["Derive the tool allowlist from surface.list_tools() instead of hardcoding names","Add a contract test: allowlisted names ⊆ registered MCP descriptor names","On tool renames, update every caller in the same change"],"tags":["codex","tool-registry","validation","naming"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}