can1357/oh-my-pi · error · Error
error
Error message
error
What it means
CollabHost.start re-parses the link it just formatted with formatCollabLink as a self-check, and throws when the parser returns an "error" result. Since the link is built from an internally generated roomId/key, this almost always means the relay URL configured for the session could not be normalized (invalid URL or unsupported/forbidden scheme).
Source
Thrown at packages/coding-agent/src/collab/host.ts:222
#sendWritablePeers(frame: CollabFrame): void {
const socket = this.#socket;
if (!socket) return;
for (const [peerId, peer] of this.#peers) {
if (peer.canWrite) socket.send(frame, peerId);
}
}
async start(relayUrl: string, webUrl = ""): Promise<void> {
const rawKey = generateRoomKey();
const writeToken = generateWriteToken();
const roomId = generateRoomId();
this.#writeToken = writeToken;
this.#link = formatCollabLink(relayUrl, roomId, rawKey, writeToken);
this.#webLink = formatCollabWebLink(relayUrl, roomId, rawKey, writeToken, webUrl);
this.#viewLink = formatCollabLink(relayUrl, roomId, rawKey);
this.#webViewLink = formatCollabWebLink(relayUrl, roomId, rawKey, undefined, webUrl);
const parsed = parseCollabLink(this.#link);
if ("error" in parsed) throw new Error(parsed.error);
const key = await importRoomKey(rawKey);
const socket = new CollabSocket({ wsUrl: parsed.wsUrl, role: "host", key });
this.#socket = socket;
this.#sessionId = this.#ctx.sessionManager.getSessionId();
const firstOpen = Promise.withResolvers<void>();
let opened = false;
socket.onOpen = () => {
if (!opened) {
opened = true;
firstOpen.resolve();
}
};
socket.onFrame = (frame, fromPeer) => this.#handleFrame(frame, fromPeer);
socket.onControl = msg => {
if (msg.t === "peer-left") this.#handlePeerLeft(msg.peer);
};View on GitHub (pinned to 9690622007)
Solutions
- Set the relay URL to a full ws:// or wss:// URL including scheme and host (e.g. wss://relay.example.com).
- Use wss:// (not ws://) for any non-localhost relay; plain ws:// is only accepted for localhost/127.0.0.1.
- Test the value with new URL(relayUrl) before saving it to config.
- Omit the setting entirely to use the default relay, which needs no normalization.
Example fix
// before relayUrl: "myrelay.example.com:8080" // not a valid absolute URL // after relayUrl: "wss://myrelay.example.com:8080"
Defensive patterns
Strategy: validation
Validate before calling
function relayUrlOk(url: string): boolean {
try {
const u = new URL(url);
if (!["ws:", "wss:", "http:", "https:"].includes(u.protocol)) return false;
if (u.protocol === "ws:" && !["localhost", "127.0.0.1", "::1"].includes(u.hostname)) return false;
return true;
} catch { return false; }
}
if (!relayUrlOk(relayUrl)) throw new Error(`bad relayUrl: ${relayUrl}`);
await host.start(relayUrl); Try / catch
try {
await host.start(relayUrl);
} catch (err) {
ui.showError(`Cannot start collaboration: ${(err as Error).message} — check relayUrl in config`);
} Prevention
- Store the relay as a full absolute ws:// or wss:// URL in config.
- Never use ws:// for non-localhost relays.
- Validate relayUrl with new URL() at config load time, before starting the host.
When it happens
Trigger: Calling CollabHost.start with a relayUrl that is not a valid URL ('', 'myrelay.example', 'localhost:9000'), uses an unsupported scheme (ftp://, wss:garbage), or uses ws:// to a non-localhost host (normalizeRelayOrigin rejects plain ws:// off localhost).
Common situations: A config file sets collab.relayUrl to a bare hostname with no scheme; a user enters 'ws://relay.example.com' for a public relay (plain ws is localhost-only); a typo like 'wss:/' or stray spaces makes new URL() throw; port-only or protocol-relative strings.
Related errors
- error
- collab.webUrl must start with http:// or https://
- resultBaseUrl is not a valid URL
- Destination option ${optionName} must use http or https
- collab.webUrl must use https:// unless it targets localhost
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a28f3f892141c25e.
Report an issue: GitHub.