Hmbown/CodeWhale · error

computer action must be list, switch, register, spawn or…

Error message

computer action must be list, switch, register, spawn or remove (got ${JSON.stringify(args.action)})

What it means

Argument-validation guard in resolveTool for the 'computer' action: args.action matched none of list/switch/register/spawn/remove in the action-to-wire-name map, so no backend command name could be resolved. The received value is shown in the message.

Solutions

  1. Use one of: list, switch, register, spawn, remove
  2. Lowercase the action value
  3. Map "connect" to "switch" and "delete" to "remove" in the caller

Example fix

// before
resolveTool("computer", { action: "connect", id: "vm1" })
// after
resolveTool("computer", { action: "switch", id: "vm1" })
Defensive patterns

Strategy: validation

Validate before calling

if (!['list','switch','register','spawn','remove'].includes(args.action)) throw new Error('computer action must be list/switch/register/spawn/remove');

Type guard

function isComputerAction(a) { return ['list','switch','register','spawn','remove'].includes(a); }

Try / catch

try { resolveTool('computer', args); } catch (e) { if (String(e.message).includes('computer action')) { /* map synonyms connect->switch, delete->remove and retry */ } else throw e; }

Prevention

When it happens

Trigger: resolveTool("computer", { action: "delete" }) or { action: "Connect" } or {} with no action.

Common situations: A model invents "connect" or "delete" (should be switch/remove); missing action field; wrong casing.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/8dc096207774da3a. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/tools.mjs:803

      if (args.action === "write") {
        if (typeof rest.text !== "string") throw bad("clipboard action \"write\" requires text");
        return { name: "write_clipboard", args: { text: rest.text, computer: rest.computer } };
      }
      throw bad(`clipboard action must be "read" or "write" (got ${JSON.stringify(args.action)})`);
    }
    case "recording": {
      const rest = { ...args };
      delete rest.action;
      const wire = { start: "recording_start", stop: "recording_stop", status: "recording_status", list: "recording_list" }[args.action];
      if (!wire) throw bad(`recording action must be start, stop, status or list (got ${JSON.stringify(args.action)})`);
      if ((args.action === "stop" || args.action === "status") && rest.id == null) throw bad(`recording action "${args.action}" requires id`);
      return { name: wire, args: rest };
    }
    case "computer": {
      const rest = { ...args };
      delete rest.action;
      const wire = { list: "computer_list", switch: "computer_switch", register: "computer_register", spawn: "computer_spawn", remove: "computer_remove" }[args.action];
      if (!wire) throw bad(`computer action must be list, switch, register, spawn or remove (got ${JSON.stringify(args.action)})`);
      if (args.action === "list") return { name: wire, args: {} };
      if (rest.id == null) throw bad(`computer action "${args.action}" requires id`);
      const id = rest.id;
      delete rest.id;
      return { name: wire, args: { ...rest, computer: id } };
    }
    case "consent": {
      const rest = { ...args };
      delete rest.action;
      const wire = { status: "consent_status", allow: "consent_allow", deny: "consent_deny", revoke: "consent_revoke" }[args.action];
      if (!wire) throw bad(`consent action must be status, allow, deny or revoke (got ${JSON.stringify(args.action)})`);
      if (args.action === "status") return { name: wire, args: { computer: rest.computer } };
      const foreground = rest.scope === "foreground";
      if (!foreground && rest.app == null && rest.name == null && rest.bundle_id == null && rest.pid == null) {
        throw bad(`consent action "${args.action}" needs an app (name, bundle_id, pid or app string) — or scope:"foreground" for the shared-pointer decision`);
      }
      return { name: wire, args: rest };
    }

View on GitHub (pinned to 73e0f67d83)