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

  1. Set the relay URL to a full ws:// or wss:// URL including scheme and host (e.g. wss://relay.example.com).
  2. Use wss:// (not ws://) for any non-localhost relay; plain ws:// is only accepted for localhost/127.0.0.1.
  3. Test the value with new URL(relayUrl) before saving it to config.
  4. 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

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


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