Hmbown/CodeWhale · error

clipboard action must be "read" or "write

Error message

clipboard action must be "read" or "write" (got ${JSON.stringify(args.action)})

What it means

The `clipboard` tool accepts exactly two actions: "read" and "write". Anything else (or an absent action) falls through both checks and is rejected with this error, since no wire tool maps the given action.

Solutions

  1. Set action to exactly "read" or "write"
  2. Lowercase the action value before calling
  3. For clearing, consider writing an empty string instead

Example fix

// before
resolveTool("clipboard", { action: "Read" })
// after
resolveTool("clipboard", { action: "read" })
Defensive patterns

Strategy: validation

Validate before calling

if (args.action !== 'read' && args.action !== 'write') throw new Error('clipboard action must be read or write');

Type guard

function isClipboardAction(a) { return a === 'read' || a === 'write'; }

Try / catch

try { resolveTool('clipboard', args); } catch (e) { if (String(e.message).includes('clipboard action')) { args.action = String(args.action || '').toLowerCase(); /* retry or report */ } else throw e; }

Prevention

When it happens

Trigger: resolveTool("clipboard", {}) with no action; action:"clear", "paste", "Read" (wrong case), or any unknown value.

Common situations: A model invents a clipboard action like "clear"; the action field is omitted; case sensitivity mistakes such as "Write".

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/4871b3b710f2c5e9. Report an issue: GitHub.

Appendix: source

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

      if (button !== "left") delete rest.strategy; // strategy is an a11y-left-click concept
      return { name: wire, args: rest };
    }
    case "pointer": {
      const rest = { ...args };
      delete rest.action;
      const wire = { move: "mouse_move", down: "left_mouse_down", up: "left_mouse_up" }[args.action];
      if (!wire) throw bad(`pointer action must be "move", "down" or "up" (got ${JSON.stringify(args.action)})`);
      return { name: wire, args: rest };
    }
    case "clipboard": {
      const rest = { ...args };
      delete rest.action;
      if (args.action === "read") return { name: "read_clipboard", args: { computer: rest.computer } };
      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;

View on GitHub (pinned to 73e0f67d83)