can1357/oh-my-pi · error · Error

error

Error message

error

What it means

CollabGuest.join parses a collaboration invite link before connecting. parseCollabLink returns a discriminated result; when it carries an "error" field, join converts it into a thrown Error with the parser's message (e.g. 'Invalid collab link: ...', 'Collab link must contain a /r/<roomId> path', 'Collab link key must be 32 (view) or 48 (full) base64url bytes'). The join is aborted because no room can be addressed without a valid roomId/wsUrl/key.

Source

Thrown at packages/coding-agent/src/collab/guest.ts:256

	/** True when this guest joined through a read-only (view) link. */
	get readOnly(): boolean {
		return this.#readOnly;
	}

	/** Shows the read-only status hint when applicable; true when the action must be dropped. */
	#rejectReadOnly(): boolean {
		if (!this.#readOnly) return false;
		this.#ctx.showStatus("This collab link is read-only");
		return true;
	}

	constructor(ctx: InteractiveModeContext) {
		this.#ctx = ctx;
	}

	async join(link: string): Promise<void> {
		const parsed = parseCollabLink(link);
		if ("error" in parsed) throw new Error(parsed.error);
		this.#roomId = parsed.roomId;
		this.#writeToken = parsed.writeToken ? Buffer.from(parsed.writeToken).toString("base64url") : undefined;
		const key = await importRoomKey(parsed.key);

		this.#returnSessionFile = this.#ctx.sessionManager.getSessionFile() ?? null;

		const socket = new CollabSocket({ wsUrl: parsed.wsUrl, role: "guest", key });
		this.#socket = socket;

		const firstWelcome = Promise.withResolvers<void>();
		let joined = false;
		this.#joinReject = err => firstWelcome.reject(err);

		const finishJoin = (): void => {
			if (joined) return;
			joined = true;
			firstWelcome.resolve();
		};

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-copy the complete invite link from the host's output — verify it ends with a long base64url key segment after the roomId.
  2. If the link came from a chat/terminal that mangled it (e.g. %23, line breaks), strip whitespace and replace %23 with #, or use the bare roomId.key form.
  3. Run parseCollabLink(link) first and surface its error to the user instead of a raw throw.
  4. Use the web deep link variant if the plain link keeps getting corrupted by click-to-open.

Example fix

// before
await guest.join(userInput); // throws opaque parsed.error
// after
const parsed = parseCollabLink(userInput);
if ("error" in parsed) {
  ui.showError("Invalid collaboration link: " + parsed.error);
  return;
}
await guest.join(userInput);
Defensive patterns

Strategy: try-catch

Validate before calling

const parsed = parseCollabLink(link);
if ("error" in parsed) {
  report(`Invalid link: ${parsed.error}`);
  return;
}

Type guard

function isParsedCollabLink(p: ReturnType<typeof parseCollabLink>): p is ParsedCollabLink {
  return !("error" in p);
}

Try / catch

try {
  await guest.join(link);
} catch (err) {
  ui.showError(`Join failed: ${err instanceof Error ? err.message : String(err)}`);
}

Prevention

When it happens

Trigger: Calling guest.join(link) with: a link whose key fragment is not valid base64url or is not 32/48 bytes; a URL missing the /r/<roomId> path; an unparseable URL string; a relay origin with an unsupported scheme (e.g. ftp://); an empty or whitespace link.

Common situations: The user pasted a partial link (key cut off by line-wrap when copying from a terminal); the link went through a chat client that percent-encoded or stripped characters; a user typed a room name instead of a full link; sharing a web deep-link fragment without the surrounding http(s) URL.

Related errors


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