{"record":{"id":"35187010249e03eb","repo":"Hmbown/CodeWhale","slug":"unknown-tool","errorCode":"unknown_tool","errorMessage":"unknown tool \"${step.tool}\"","messagePattern":"unknown tool \"(.+?)\"","errorType":"error_code","errorClass":"ServerError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/mcp/server.mjs","lineNumber":856,"sourceCode":"  let binding;\n  let dispatched = false;\n  try {\n    binding = await bindComputer(computer);\n    if (binding.needsObservation && !ROUTE_INSPECTION_TOOLS.has(name)) {\n      throw new ServerError(\"computer_observation_required\", \"Computer route changed — call screenshot or get_app_state on the registered target before acting\");\n    }\n    // Per-app consent: the first call that targets an application on the local\n    // computer must carry a recorded user decision. open_application returns\n    // the grant so its resolved identity can be aliased below.\n    const gateResult = await consentCheck(computer, name, args);\n    if (name === \"run_actions\") {\n      const steps = args.steps;\n      if (!Array.isArray(steps) || steps.length < 1 || steps.length > 8) throw new ServerError(\"bad_args\", \"run_actions needs 1..8 steps\");\n      const results = [];\n      for (const [i, step] of steps.entries()) {\n        if (!step || typeof step.tool !== \"string\") throw new ServerError(\"bad_args\", `step ${i} needs a tool name`);\n        if (step.tool === \"run_actions\") throw new ServerError(\"bad_args\", \"run_actions cannot nest\");\n        if (!TOOL_NAMES.has(step.tool)) throw new ServerError(\"unknown_tool\", `unknown tool \"${step.tool}\"`);\n        const result = await callTool({ name: step.tool, arguments: { ...(step.arguments ?? {}), computer: computer.id } });\n        const body = JSON.parse(result.content[0].text);\n        results.push({ tool: step.tool, ok: body.ok !== false, receipt: body });\n        if (body.ok === false || result.isError) {\n          return { content: [{ type: \"text\", text: JSON.stringify(fail(computer, body.error?.code ?? \"step_failed\", body.error?.message ?? \"step failed\", { tool: \"run_actions\", switched, stopped_at: i, steps: results })) }], isError: true };\n        }\n      }\n      return { content: [{ type: \"text\", text: JSON.stringify(receipt(computer, { ok: true, tool: \"run_actions\", switched, steps: results })) }] };\n    }\n    if (name === \"find_elements\") {\n      const st = args.state_id ? appStates.get(args.state_id) : null;\n      if (args.state_id && !st) throw new ServerError(\"unknown_state\", `state_id \"${args.state_id}\" is unknown or expired — call get_app_state again`);\n      if (st) {\n        if (st.computerId && st.computerId !== computer.id) {\n          throw new ServerError(\"state_wrong_computer\", `state_id \"${args.state_id}\" belongs to computer \"${st.computerId}\", not \"${computer.id}\"`);\n        }\n        const filtered = filterElements(st.elements, {\n          detail: \"summary\", query: args.query, role: args.role,","sourceCodeStart":838,"sourceCodeEnd":874,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/mcp/server.mjs#L838-L874","documentation":"run_actions validates each step's tool name against the server's registered TOOL_NAMES set. Any step referencing a tool the MCP server does not expose (typo, removed/renamed tool, or the nesting-disallowed run_actions itself caught earlier) throws unknown_tool. It exists so a batched sequence fails fast at validation rather than mid-dispatch.","triggerScenarios":"Calling run_actions with a steps array whose step.tool is not in TOOL_NAMES — e.g. misspelled 'screenshots', a tool name from a different MCP server, or an alias the server never registered.","commonSituations":"Model or client hallucinating tool names; renaming tools across server versions while cached prompts still reference old names; copy-pasting step lists between computer-use servers with different tool catalogs.","solutions":["Check the server's tool list (tools/list or TOOL_NAMES) and use an exact registered name for every step","Fix the typo in step.tool","Split the batch: run only steps with valid tools and replace or drop the invalid one","If a needed tool is genuinely missing, upgrade the computer-use plugin/server to a version that exposes it"],"exampleFix":"// before\nsteps: [{ tool: \"screenshots\" }]\n// after\nsteps: [{ tool: \"screenshot\" }]","handlingStrategy":"validation","validationCode":"const TOOL_NAMES = new Set([\"screenshot\",\"zoom\",\"left_click\",\"type\",\"key\",\"get_app_state\",\"find_elements\",\"focus\",\"run_actions\",\"app_script\"]); // use the server's actual tool list\nif (!steps.every(s => typeof s?.tool === \"string\" && TOOL_NAMES.has(s.tool))) throw new Error(\"run_actions step has unregistered tool\");","typeGuard":"const isValidStep = (s) => !!s && typeof s.tool === \"string\" && TOOL_NAMES.has(s.tool);","tryCatchPattern":"try { await runActions({ steps }) } catch (e) { if (e.code === \"unknown_tool\") { /* fix or drop steps where !TOOL_NAMES.has(step.tool) */ } else throw e; }","preventionTips":["Fetch tools/list at session start and validate step names against it","Centralize tool-name constants instead of hardcoding strings in prompts","Re-validate cached step scripts after server upgrades"],"tags":["mcp","computer-use","validation"],"backgroundTag":"invalid-enum-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}