different-ai/openwork · error

Only valid web links can be opened externally.

Error message

Only valid web links can be opened externally.

What it means

assertDesktopWebUrl validates a URL before it is handed to the OS shell opener. If `new URL(url)` throws — meaning the string is not a parseable absolute URL — the function refuses to proceed with the generic message 'Only valid web links can be opened externally.' This prevents garbage or relative strings from reaching openExternal/window.open.

Source

Thrown at apps/app/src/app/lib/desktop.ts:475

): Promise<Response> {
  if (isLoopbackUrl(input)) {
    return globalThis.fetch(input, init);
  }
  return desktopFetchThroughMain(input, init, {
    agentContextDiagnosticsDeadlineAtMs: deadlineAtMs,
  });
}

// ---------------------------------------------------------------------------
// Convenience wrappers
// ---------------------------------------------------------------------------

export function assertDesktopWebUrl(url: string): string {
  let parsed: URL;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error("Only valid web links can be opened externally.");
  }
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
    throw new Error(`External URL protocol "${parsed.protocol}" is not allowed.`);
  }
  return parsed.toString();
}

export async function openDesktopUrl(url: string): Promise<void> {
  const safeUrl = assertDesktopWebUrl(url);
  const openExternal = window.__OPENWORK_ELECTRON__?.shell?.openExternal;
  if (openExternal) {
    const result = await openExternal(safeUrl);
    if (result && result.ok === false) {
      throw new Error(result.error ?? "Failed to open browser");
    }
    return;
  }
  if (typeof window !== "undefined") {

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Ensure the input is an absolute URL including scheme (https://...) before passing it in; prepend a base when the source is relative (`new URL(rel, location.origin).toString()`).
  2. Trim whitespace and strip stray characters (quotes, trailing punctuation) from user-supplied or copy-pasted links.
  3. Validate the string with a quick `URL.canParse(url)` (or try/catch around `new URL`) in form validation so the user fixes it before the open attempt.
  4. Check where the URL originates (config file, env var, API field) and fix the producer to emit full web URLs.

Example fix

// before
await openDesktopUrl(userInput);

// after
const target = userInput.startsWith("http") ? userInput : `https://${userInput}`;
if (!URL.canParse(target)) throw new Error("Please enter a valid link");
await openDesktopUrl(target);
Defensive patterns

Strategy: validation

Validate before calling

function isOpenableWebUrl(url: string): boolean {
  try { const u = new URL(url); return u.protocol === "http:" || u.protocol === "https:"; } catch { return false; }
}

Try / catch

try {
  await openDesktopUrl(link);
} catch (err) {
  if (err instanceof Error && err.message.includes("Only valid web links")) {
    showToast("That is not a valid link");
  } else { throw err; }
}

Prevention

When it happens

Trigger: Calling openDesktopUrl, safeUrl, or waitForManagedMcpAuthorization with a non-URL string: an empty string, a relative path like '/settings', a malformed value like 'http//example.com', or user-pasted text that is not a URL.

Common situations: A config field (e.g. server URL or MCP authorize URL) left blank or containing a typo; reading a link from storage or an API response where it was stored relative instead of absolute; user input from a form passed straight to the opener without trimming/normalizing; locale-mangled copy-paste breaking the scheme.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/63bc5e15e593e794. Report an issue: GitHub.