Hmbown/CodeWhale · error · ExecError
open_application url must be an absolute URL
Error message
open_application url must be an absolute URL
What it means
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.
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
Example fix
// before
await openApplication({ name: "chrome", url: "example.com" });
// after
await openApplication({ name: "chrome", url: "https://example.com" }); Defensive patterns
Strategy: validation
Validate before calling
function isValidLaunchUrl(url) {
return typeof url === "string"
&& URL.canParse(url)
&& !/[\0\r\n]/.test(url);
}
// usage
if (url != null && !isValidLaunchUrl(url)) throw new Error("url must be an absolute URL without control chars"); Type guard
const isSafeUrl = (v) => typeof v === "string" && URL.canParse(v) && !/[\0\r\n]/.test(v);
Try / catch
try {
await backend.open_application({ name: "chrome", url });
} catch (e) {
if (String(e.message).includes("absolute URL")) {
const fixed = /^https?:\/\//i.test(url) ? url : `https://${url}`;
await backend.open_application({ name: "chrome", url: fixed });
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Forgetting https:// when handing a domain to a browser; scraped URLs containing stray \r\n; agents constructing urls from user text without sanitization.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- --base-url must use http or https
- browser URL cannot be empty
- Codewhale account API base URL must be an origin without a…
- Codewhale account API base URL must not contain a query or…
- returned an unusable verification URI
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/7820d1c96b18e246.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/win32.mjs:342
}
}
'@;
$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 = {}) => {
if (Object.hasOwn(args, "window_id")) throw unsupportedSelector("Windows get_app_state does not support window_id");
const { app_ref, detail } = args;
if (Object.hasOwn(args, "app_ref") && (!app_ref || typeof app_ref !== "object" || Array.isArray(app_ref)View on GitHub (pinned to 73e0f67d83)