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

  1. Prefix the scheme: use "https://example.com" not "example.com"
  2. Trim and strip control characters (\r, \n, \0) from the URL before passing it
  3. Validate with URL.canParse(url) on the caller side before invoking
  4. 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

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


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)