{"record":{"id":"bb4dce92bc70cf29","repo":"Hmbown/CodeWhale","slug":"bad-args-browser-navigate-start-need-a-url-http-or-https-or","errorCode":"bad_args","errorMessage":"browser navigate/start need a url (http:// or https:// or about:blank)","messagePattern":"browser navigate/start need a url \\(http:// or https:// or about:blank\\)","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/browser-cdp.mjs","lineNumber":36,"sourceCode":"import path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { spawn } from \"node:child_process\";\nimport { ExecError, currentSignal } from \"./exec.mjs\";\nimport { stateDir } from \"./registry.mjs\";\n\nconst APPLICATIONS = [\"Google Chrome\", \"Chromium\", \"Brave Browser\", \"Microsoft Edge\"];\nconst LINUX_BINARIES = [\"google-chrome\", \"chromium\", \"chromium-browser\", \"brave-browser\", \"microsoft-edge\"];\n\nconst sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));\nconst badArgs = (message) => Object.assign(new ExecError(message), { code: \"bad_args\" });\n\nfunction defaultRecordingsDir() {\n  return process.env.CODEWHALE_CU_RECORDINGS_DIR || path.join(stateDir(), \"recordings\");\n}\n\n/** Only http(s) and about:blank can be navigated to; everything else is refused. */\nexport function checkBrowserUrl(url) {\n  if (typeof url !== \"string\" || !url.trim()) throw badArgs(\"browser navigate/start need a url (http:// or https:// or about:blank)\");\n  const trimmed = url.trim();\n  if (/^about:blank$/i.test(trimmed)) return trimmed;\n  let parsed;\n  try { parsed = new URL(trimmed); } catch { throw badArgs(`\"${trimmed}\" is not a URL`); }\n  if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n    throw badArgs(`only http(s):// and about:blank URLs can be opened (got \"${parsed.protocol}//\")`);\n  }\n  return parsed.href;\n}\n\n/** Locate a Chromium-family browser app or binary; CODEWHALE_CU_BROWSER_APP overrides. */\nexport function findBrowserApp(platform = process.platform, env = process.env, exists = fs.existsSync) {\n  if (env.CODEWHALE_CU_BROWSER_APP) return env.CODEWHALE_CU_BROWSER_APP;\n  if (platform === \"darwin\") {\n    for (const name of APPLICATIONS) {\n      for (const root of [\"/Applications\", path.join(os.homedir(), \"Applications\")]) {\n        const candidate = path.posix.join(root, `${name}.app`);\n        if (exists(candidate)) return candidate;","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/browser-cdp.mjs#L18-L54","documentation":"checkBrowserUrl validates the url argument for browser navigate/start. An empty string, whitespace-only value, or non-string (undefined/null) fails the first guard and throws badArgs demanding a URL. The plugin only allows http://, https://, and about:blank targets.","triggerScenarios":"Calling browser navigate or browser start without a url argument, with url: \"\", or with a whitespace-only url.","commonSituations":"An LLM omitting the url field; a form/config value that is empty; constructing the action object dynamically and leaving url undefined; forgetting that file:// and chrome:// are also rejected here (different message) but empty input hits this one.","solutions":["Provide a url argument with a valid http:// or https:// URL, or the literal about:blank.","If the target is a local file, serve it over http (e.g. a local dev server) instead of file://.","Trim user/config input before passing; an all-whitespace value is treated as missing.","Validate the field exists before dispatching the browser action."],"exampleFix":"// before\nawait browser({ action: \"navigate\", url: \"\" });\n// after\nawait browser({ action: \"navigate\", url: \"https://example.com\" });","handlingStrategy":"validation","validationCode":"if (typeof url !== \"string\" || !url.trim()) throw new Error(\"navigate/start require a non-empty url\");","typeGuard":"const hasUrl = (a) => typeof a?.url === \"string\" && a.url.trim().length > 0;","tryCatchPattern":"try { await browser(action) } catch (e) { if (String(e.message).includes(\"need a url\")) { throw new Error(`caller bug: missing url for ${action.action}`); } throw e; }","preventionTips":["Validate the url field before dispatching browser actions","Trim config/user input before assigning url","Default to about:blank when no target is intended"],"tags":["browser","cdp","validation","missing-argument"],"backgroundTag":"missing-required-argument","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}