Hmbown/CodeWhale · error · ExecError

open_application needs a plain app or executable name

Error message

open_application needs a plain app or executable name

What it means

open_application validates that the launch target (name or bundle_id) is a plain app/executable name matching /^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/. It throws this ExecError when the target is missing, empty, non-string, or contains characters (paths, quotes, shell metacharacters) that could break or inject into the PowerShell Start-Process command line.

Solutions

  1. Pass a bare executable or app name, e.g. "notepad" or "chrome", not a full path or command line
  2. Pass URLs via the separate url parameter, not as the name
  3. Ensure either name or bundle_id is a non-empty string of letters, digits, and . : _ - or space
  4. Catch ExecError and re-prompt the caller with the accepted name format

Example fix

// before
await openApplication({ name: "C:\\Program Files\\Mozilla Firefox\\firefox.exe" });
// after
await openApplication({ name: "firefox" });
Defensive patterns

Strategy: validation

Validate before calling

const APP_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/;
function validateAppName(args) {
  const target = args?.name ?? args?.bundle_id;
  if (typeof target !== "string" || !APP_NAME_RE.test(target)) {
    throw new TypeError("open_application requires a bare app/executable name (letters, digits, . : _ - and spaces)");
  }
  return target;
}

Type guard

const isPlainAppName = (v) =>
  typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/.test(v);

Try / catch

try {
  await backend.open_application({ name });
} catch (e) {
  if (String(e.message).includes("plain app or executable name")) {
    throw new Error(`"${name}" is not a bare app name; strip paths/arguments/URLs`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling open_application with name like "C:\\Program Files\\app.exe", "app.exe --flag", an empty string, null/undefined with no bundle_id, or names containing slashes, quotes, or Unicode characters.

Common situations: Passing full executable paths instead of bare names; trying to pass CLI arguments through the name field; empty inputs from an agent; attempting shell injection which the regex deliberately blocks.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:339

      return true;
    }, IntPtr.Zero);
    return result;
  }
}
'@;
$json = [WinEnum]::List() | ForEach-Object { $p = $_.Split('|', 2); $parts = $p[1].Split('|', 2); [pscustomobject]@{ pid2 = [int]$p[0]; geom = $parts[0]; title = $parts[1] } } | ConvertTo-Json -Compress;
if (-not $json) { $json = '[]' }
Write-Output ('{"windows": ' + $json + '}');`, { timeoutMs: 25_000 });
      return {
        windows: (Array.isArray(j.windows) ? j.windows : [j.windows]).map((w) => {
          const g = String(w.geom).split(",").map(Number);
          return { pid: w.pid2, title: w.title, position: { x: g[0], y: g[1] }, size: { w: g[2], h: g[3] } };
        }),
      };
    },
    open_application: async ({ name, bundle_id: bid, url: urlArg, activate } = {}) => {
      const target = name ?? bid;
      if (typeof target !== "string" || !/^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/.test(target)) throw new ExecError("open_application needs a plain app or executable name");
      let argumentsScript = "";
      if (urlArg != null) {
        if (typeof urlArg !== "string" || !URL.canParse(urlArg) || /[\0\r\n]/.test(urlArg)) throw new ExecError("open_application url must be an absolute URL");
        // Start-Process joins ArgumentList into a Windows command line. Quote
        // one argument there, and transport that string as data into PowerShell.
        const quoted = '"' + urlArg.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/g, '$1$1') + '"';
        const encoded = Buffer.from(quoted, "utf16le").toString("base64");
        argumentsScript = `$launchArg = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encoded}')); `;
      }
      // activate defaults to background on every platform: a minimized
      // launch leaves the user's foreground window alone. Windows input is
      // still shared-surface — this only controls the launch, not input.
      const windowStyle = activate === true ? "" : " -WindowStyle Minimized";
      const r = await psOk(`${argumentsScript}Start-Process -FilePath "${target}"${windowStyle}${urlArg != null ? " -ArgumentList $launchArg" : ""}; Write-Output '{"launched": true}'`, { timeoutMs: 20_000 });
      if (r.code !== 0) throw new ExecError(`Start-Process failed: ${r.stderr.trim().slice(0, 200)}`, r);
      return { launched: true, name: target, url: urlArg ?? null, activate: activate === true };
    },
    get_app_state: async (args = {}) => {

View on GitHub (pinned to 73e0f67d83)