{"record":{"id":"7820d1c96b18e246","repo":"Hmbown/CodeWhale","slug":"open-application-url-must-be-an-absolute-url","errorCode":null,"errorMessage":"open_application url must be an absolute URL","messagePattern":"open_application url must be an absolute URL","errorType":"validation","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/win32.mjs","lineNumber":342,"sourceCode":"  }\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 = {}) => {\n      if (Object.hasOwn(args, \"window_id\")) throw unsupportedSelector(\"Windows get_app_state does not support window_id\");\n      const { app_ref, detail } = args;\n      if (Object.hasOwn(args, \"app_ref\") && (!app_ref || typeof app_ref !== \"object\" || Array.isArray(app_ref)","sourceCodeStart":324,"sourceCodeEnd":360,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/win32.mjs#L324-L360","documentation":"When open_application receives a url argument it must be a parseable absolute URL (URL.canParse) with no NUL, CR, or LF characters. It throws this ExecError otherwise, because the URL is embedded as a quoted argument passed to Start-Process and a relative or control-character-laden value would launch incorrectly or enable argument injection.","triggerScenarios":"Calling open_application({name:\"chrome\", url:\"example.com\"}) without a scheme; url:\"\"; urls containing newline/carriage-return characters from untrusted input; passing a non-string value like a URL object or number.","commonSituations":"Forgetting https:// when handing a domain to a browser; scraped URLs containing stray \\r\\n; agents constructing urls from user text without sanitization.","solutions":["Prefix the scheme: use \"https://example.com\" not \"example.com\"","Trim and strip control characters (\\r, \\n, \\0) from the URL before passing it","Validate with URL.canParse(url) on the caller side before invoking","Pass the URL as a string, not a URL object"],"exampleFix":"// before\nawait openApplication({ name: \"chrome\", url: \"example.com\" });\n// after\nawait openApplication({ name: \"chrome\", url: \"https://example.com\" });","handlingStrategy":"validation","validationCode":"function isValidLaunchUrl(url) {\n  return typeof url === \"string\"\n    && URL.canParse(url)\n    && !/[\\0\\r\\n]/.test(url);\n}\n// usage\nif (url != null && !isValidLaunchUrl(url)) throw new Error(\"url must be an absolute URL without control chars\");","typeGuard":"const isSafeUrl = (v) => typeof v === \"string\" && URL.canParse(v) && !/[\\0\\r\\n]/.test(v);","tryCatchPattern":"try {\n  await backend.open_application({ name: \"chrome\", url });\n} catch (e) {\n  if (String(e.message).includes(\"absolute URL\")) {\n    const fixed = /^https?:\\/\\//i.test(url) ? url : `https://${url}`;\n    await backend.open_application({ name: \"chrome\", url: fixed });\n  } else throw e;\n}","preventionTips":["Always include a scheme (https://) in URLs","Sanitize user-derived URLs: strip \\r, \\n, \\0","Validate with URL.canParse before invoking","Pass strings, not URL objects"],"tags":["url","validation","windows"],"backgroundTag":"invalid-url-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"}