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
- Edit the collab.webUrl config value and strip everything from '?' and '#' onward, keeping only scheme://host[:port]/path
- Verify with a quick check: new URL(value).search === '' && new URL(value).hash === ''
- 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
- Paste only scheme://host/path into collab.webUrl — never copy the browser address bar verbatim
- Validate config at load time with the URL checks before the app starts
- Keep auth/query params in dedicated config keys, not the base URL
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
- ${destination} returned an invalid upload URL
- Failed to load tree-sitter language: {err}
- Provider delete URL must not embed an account credential
- Destination option ${key} must be a string
- Destination option ${key} must be a finite number
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3d6b80dcbca8ec97.
Report an issue: GitHub.