Hmbown/CodeWhale · error

consent action " " needs an app (name, bundle_id, pid or…

Error message

consent action "${args.action}" needs an app (name, bundle_id, pid or app string) — or scope:"foreground" for the shared-pointer decision

What it means

For consent actions allow/deny/revoke, the target app must be identified: either scope:"foreground" (the shared-pointer decision) or one of app, name, bundle_id, or pid. When none is supplied the resolver throws this descriptive error rather than sending an untargeted consent change.

Solutions

  1. Add the target app identification: app (string), name, bundle_id, or pid
  2. Set scope:"foreground" if the decision applies to the foreground/shared-pointer app
  3. Fix scope casing to lowercase "foreground" if that was the intent

Example fix

// before
resolveTool("consent", { action: "deny", scope: "Foreground" })
// after
resolveTool("consent", { action: "deny", scope: "foreground" }) // or add app: "Notes"
Defensive patterns

Strategy: validation

Validate before calling

function consentTargeted(a) {
  if (a.action === 'status') return true;
  return a.scope === 'foreground' || a.app != null || a.name != null || a.bundle_id != null || a.pid != null;
}
if (!consentTargeted(args)) throw new Error('consent allow/deny/revoke needs app or scope:"foreground"');

Type guard

function hasConsentTarget(a) { return ['app','name','bundle_id','pid'].some(k => a[k] != null) || a.scope === 'foreground'; }

Try / catch

try { resolveTool('consent', args); } catch (e) { if (String(e.message).includes('needs an app')) { /* prompt for target app or set scope:'foreground' */ } else throw e; }

Prevention

When it happens

Trigger: resolveTool("consent", { action: "allow" }) with no app/name/bundle_id/pid and scope not "foreground"; scope misspelled as e.g. "active" so the foreground shortcut is not taken.

Common situations: A model wants to grant consent globally without naming an app (unsupported); scope:"Foreground" wrong case so it is not recognized; the app fields are put under a nested object instead of top-level.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

      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 };
    }
    case "key": {
      if (args.duration == null) return { name, args };
      const { duration, repeat, target, ...rest } = args;
      if (repeat != null || target != null) throw bad("key with duration holds the key — repeat and target cannot be combined with it");
      if (!Number.isFinite(duration) || duration < 0.05 || duration > 30) throw bad("duration must be 0.05..30 seconds");
      return { name: "hold_key", args: { ...rest, duration } };
    }
    case "browser": {
      const rest = { ...args };
      delete rest.action;
      switch (args.action) {
        case "start":
          if (rest.url != null && typeof rest.url !== "string") throw bad("browser start url must be a string");
          return { name: "browser_start", args: rest };
        case "status": return { name: "browser_status", args: rest };

View on GitHub (pinned to 73e0f67d83)