can1357/oh-my-pi · error · Error

collab.webUrl must not include a query string or fragment

Error message

collab.webUrl must not include a query string or fragment

What it means

normalizeCollabWebBaseUrl validates the collab.webUrl config value used to build web links for collaboration sessions. The library requires a clean base URL: any query string ('?...') or fragment ('#...') makes it impossible to deterministically append session paths, so it throws. It also enforces http(s) scheme and https-for-non-localhost, but this specific error is the query/fragment check.

Source

Thrown at packages/coding-agent/src/collab/protocol.ts:230

		return normalized.origin.startsWith("wss://")
			? `https://${normalized.origin.slice("wss://".length)}`
			: `http://${normalized.origin.slice("ws://".length)}`;
	}

	let url: URL;
	try {
		url = new URL(explicitWebUrl);
	} catch {
		throw new Error("collab.webUrl must start with http:// or https://");
	}
	if (url.protocol !== "http:" && url.protocol !== "https:") {
		throw new Error("collab.webUrl must start with http:// or https://");
	}
	if (url.protocol === "http:" && !isLocalHostname(url.hostname)) {
		throw new Error("collab.webUrl must use https:// unless it targets localhost");
	}
	if (url.search || url.hash) {
		throw new Error("collab.webUrl must not include a query string or fragment");
	}
	const path = url.pathname.replace(/\/+$/, "");
	return `${url.origin}${path}`;
}

/**
 * Render the browser deep link. The browser UI may be hosted separately from
 * the relay; the fragment always carries the relay-specific collab link, so
 * room secrets stay out of HTTP path and query bytes.
 */
export function formatCollabWebLink(
	relayUrl: string,
	roomId: string,
	key: Uint8Array,
	writeToken?: Uint8Array,
	webUrl?: string,
): string {
	return `${normalizeCollabWebBaseUrl(relayUrl, webUrl)}/#${formatCollabLink(relayUrl, roomId, key, writeToken)}`;

View on GitHub (pinned to 9690622007)

Solutions

  1. Edit the collab.webUrl config value and strip everything from '?' and '#' onward, keeping only scheme://host[:port]/path
  2. Verify with a quick check: new URL(value).search === '' && new URL(value).hash === ''
  3. If auth/query data is needed, pass it via environment or a dedicated config key, not the base URL

Example fix

// before
collab.webUrl = "https://collab.example.com/?org=acme#/board"
// after
collab.webUrl = "https://collab.example.com"
Defensive patterns

Strategy: validation

Validate before calling

function isValidCollabWebUrl(v) {
  try {
    const u = new URL(v);
    return (u.protocol === 'https:' || (u.protocol === 'http:' && /^(localhost|127\.|\[::1\])/.test(u.hostname))) && !u.search && !u.hash;
  } catch { return false; }
}
// call before assigning collab.webUrl

Type guard

function isCleanHttpUrl(v) {
  if (typeof v !== 'string') return false;
  try {
    const u = new URL(v);
    return (u.protocol === 'https:' || u.protocol === 'http:') && u.search === '' && u.hash === '';
  } catch { return false; }
}

Try / catch

try {
  const base = normalizeCollabWebBaseUrl(config.collab.webUrl);
} catch (err) {
  if (err instanceof Error && err.message.includes('collab.webUrl')) {
    logger.warn('collab.webUrl invalid, using default', { value: config.collab.webUrl });
    return DEFAULT_COLLAB_WEB_URL;
  }
  throw err;
}

Prevention

When it happens

Trigger: Setting collab.webUrl in config to a URL like 'https://collab.example.com/?org=acme' or 'https://collab.example.com/#/dashboard'; formatCollabWebLink calls normalizeCollabWebBaseUrl on every link render, so any session that renders a collab web link fails until the config is fixed.

Common situations: Copying a full page URL from a browser address bar (which often carries ?token= or #/ routes) and pasting it into collab.webUrl; including tracking parameters appended by a web app.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/3d6b80dcbca8ec97. Report an issue: GitHub.