{"record":{"id":"90f8d3e463143f22","repo":"Hmbown/CodeWhale","slug":"open-application-needs-a-plain-executable-desktop-name","errorCode":null,"errorMessage":"open_application needs a plain executable/desktop name","messagePattern":"open_application needs a plain executable/desktop name","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/linux.mjs","lineNumber":464,"sourceCode":"        const windows = [];\n        const walk = (n) => {\n          if (n.type === \"con\" && n.name) windows.push({ id: String(n.id), title: n.name, wm_class: n.app_id ?? null, position: { x: n.rect?.x, y: n.rect?.y }, size: { w: n.rect?.width, h: n.rect?.height }, focused: !!n.focused });\n          (n.nodes ?? []).forEach(walk);\n          (n.floating_nodes ?? []).forEach(walk);\n        };\n        walk(tryJson(r.stdout, {}));\n        return { windows };\n      }\n      if (session === \"wayland\" && tools.hyprctl) {\n        const r = await run(\"hyprctl\", [\"-j\", \"clients\"], { timeoutMs: 15_000 });\n        const clients = tryJson(r.stdout, []);\n        return { windows: clients.map((c) => ({ id: String(c.address), title: c.title, wm_class: c.class, position: { x: c.at?.[0], y: c.at?.[1] }, size: { w: c.size?.[0], h: c.size?.[1] }, focused: !!c.focused })) };\n      }\n      throw new ExecError(\"list_windows needs wmctrl (X11), swaymsg (sway) or hyprctl (hyprland)\");\n    },\n    open_application: async ({ name, bundle_id: bid, url: urlArg, activate } = {}) => {\n      const target = name ?? bid;\n      if (!target || !/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(target)) throw new ExecError(\"open_application needs a plain executable/desktop name\");\n      // activate defaults to background: on X11 a new window grabs focus, so\n      // remember the active window and hand focus back after the launch.\n      let prevWindow = null;\n      if (activate !== true) {\n        try {\n          await probeSession();\n          if (session === \"x11\" && tools.xdotool) {\n            const active = await run(\"xdotool\", [\"getactivewindow\"], { timeoutMs: 3_000 });\n            if (active.code === 0 && /^\\d+$/.test(active.stdout.trim())) prevWindow = active.stdout.trim();\n          }\n        } catch { /* no session/tools — the launch itself is still fine */ }\n      }\n      spawnDetached(target, urlArg ? [urlArg] : [], \"\", true);\n      await new Promise((r) => setTimeout(r, 500));\n      let focusRestored = false;\n      if (prevWindow) {\n        try {\n          focusRestored = (await run(\"xdotool\", [\"windowactivate\", prevWindow], { timeoutMs: 3_000 })).code === 0;","sourceCodeStart":446,"sourceCodeEnd":482,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/linux.mjs#L446-L482","documentation":"The linux backend's open_application only launches plain executable or desktop-entry names (e.g. 'firefox', 'org.gnome.Nautilus'). The library throws this when the resolved target (name or bundle_id) is missing or contains characters outside letters, digits, space, dot, underscore, dash — a guard against shell/special-character injection and misuse of macOS-style bundle IDs or URLs.","triggerScenarios":"Calling open_application with no name/bundle_id; passing a bundle_id that is only used on macOS (e.g. 'com.apple.Safari'); passing a URL to urlArg while name and bundle_id are absent; names containing '/', ':', '@', quotes, or starting with a non-alphanumeric character.","commonSituations":"Porting cross-platform automation code that reuses macOS bundle identifiers; trying to open a website by URL on Linux where that parameter is ignored; building the name from user input containing paths like '/usr/bin/firefox' or shell metacharacters.","solutions":["Pass a plain executable or desktop-entry name, e.g. open_application({ name: 'firefox' }).","Strip path prefixes and shell metacharacters from the target before calling.","To open a URL, launch the browser app by name with the URL handled separately instead of relying on urlArg.","If the app only has a desktop entry, use its desktop file name (e.g. 'org.gnome.Nautilus')."],"exampleFix":"// before\nawait backend.open_application({ name: '/usr/bin/google-chrome --incognito' });\n// after\nawait backend.open_application({ name: 'google-chrome' });","handlingStrategy":"validation","validationCode":"const target = opts.name ?? opts.bundle_id;\nif (!/^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(target ?? '')) throw new Error('open_application needs a plain executable/desktop name');","typeGuard":"const isValidAppTarget = (t) => typeof t === 'string' && /^[A-Za-z0-9][A-Za-z0-9 ._-]*$/.test(t);","tryCatchPattern":null,"preventionTips":["Validate app names against the same regex the backend uses before calling","Never interpolate paths or flags into the name field","Keep a per-platform launch map: bundle_id for macOS, desktop names for Linux"],"tags":["validation","linux","computer-use","argument-validation"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T21:17:16.096Z"}