{"record":{"id":"2a671746170ac233","repo":"Hmbown/CodeWhale","slug":"open-application-needs-a-plain-app-or-executable-name","errorCode":null,"errorMessage":"open_application needs a plain app or executable name","messagePattern":"open_application needs a plain app or executable name","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/win32.mjs","lineNumber":339,"sourceCode":"      return true;\n    }, IntPtr.Zero);\n    return result;\n  }\n}\n'@;\n$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;\nif (-not $json) { $json = '[]' }\nWrite-Output ('{\"windows\": ' + $json + '}');`, { timeoutMs: 25_000 });\n      return {\n        windows: (Array.isArray(j.windows) ? j.windows : [j.windows]).map((w) => {\n          const g = String(w.geom).split(\",\").map(Number);\n          return { pid: w.pid2, title: w.title, position: { x: g[0], y: g[1] }, size: { w: g[2], h: g[3] } };\n        }),\n      };\n    },\n    open_application: async ({ name, bundle_id: bid, url: urlArg, activate } = {}) => {\n      const target = name ?? bid;\n      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\");\n      let argumentsScript = \"\";\n      if (urlArg != null) {\n        if (typeof urlArg !== \"string\" || !URL.canParse(urlArg) || /[\\0\\r\\n]/.test(urlArg)) throw new ExecError(\"open_application url must be an absolute URL\");\n        // Start-Process joins ArgumentList into a Windows command line. Quote\n        // one argument there, and transport that string as data into PowerShell.\n        const quoted = '\"' + urlArg.replace(/(\\\\*)\"/g, '$1$1\\\\\"').replace(/(\\\\+)$/g, '$1$1') + '\"';\n        const encoded = Buffer.from(quoted, \"utf16le\").toString(\"base64\");\n        argumentsScript = `$launchArg = [Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('${encoded}')); `;\n      }\n      // activate defaults to background on every platform: a minimized\n      // launch leaves the user's foreground window alone. Windows input is\n      // still shared-surface — this only controls the launch, not input.\n      const windowStyle = activate === true ? \"\" : \" -WindowStyle Minimized\";\n      const r = await psOk(`${argumentsScript}Start-Process -FilePath \"${target}\"${windowStyle}${urlArg != null ? \" -ArgumentList $launchArg\" : \"\"}; Write-Output '{\"launched\": true}'`, { timeoutMs: 20_000 });\n      if (r.code !== 0) throw new ExecError(`Start-Process failed: ${r.stderr.trim().slice(0, 200)}`, r);\n      return { launched: true, name: target, url: urlArg ?? null, activate: activate === true };\n    },\n    get_app_state: async (args = {}) => {","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/win32.mjs#L321-L357","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a bare executable or app name, e.g. \"notepad\" or \"chrome\", not a full path or command line","Pass URLs via the separate url parameter, not as the name","Ensure either name or bundle_id is a non-empty string of letters, digits, and . : _ - or space","Catch ExecError and re-prompt the caller with the accepted name format"],"exampleFix":"// before\nawait openApplication({ name: \"C:\\\\Program Files\\\\Mozilla Firefox\\\\firefox.exe\" });\n// after\nawait openApplication({ name: \"firefox\" });","handlingStrategy":"validation","validationCode":"const APP_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/;\nfunction validateAppName(args) {\n  const target = args?.name ?? args?.bundle_id;\n  if (typeof target !== \"string\" || !APP_NAME_RE.test(target)) {\n    throw new TypeError(\"open_application requires a bare app/executable name (letters, digits, . : _ - and spaces)\");\n  }\n  return target;\n}","typeGuard":"const isPlainAppName = (v) =>\n  typeof v === \"string\" && /^[A-Za-z0-9][A-Za-z0-9 .:_-]*$/.test(v);","tryCatchPattern":"try {\n  await backend.open_application({ name });\n} catch (e) {\n  if (String(e.message).includes(\"plain app or executable name\")) {\n    throw new Error(`\"${name}\" is not a bare app name; strip paths/arguments/URLs`);\n  }\n  throw e;\n}","preventionTips":["Pass bare names like \"notepad\", never full paths or command lines","Use the url parameter for URLs, not the name field","Never embed user-supplied shell text in the name","Discover valid names via list_apps"],"tags":["validation","injection-guard","windows"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}