{"record":{"id":"c4671efd975df2fb","repo":"can1357/oh-my-pi","slug":"error-c4671e","errorCode":null,"errorMessage":"error","messagePattern":"error","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/collab/guest.ts","lineNumber":256,"sourceCode":"\t/** True when this guest joined through a read-only (view) link. */\n\tget readOnly(): boolean {\n\t\treturn this.#readOnly;\n\t}\n\n\t/** Shows the read-only status hint when applicable; true when the action must be dropped. */\n\t#rejectReadOnly(): boolean {\n\t\tif (!this.#readOnly) return false;\n\t\tthis.#ctx.showStatus(\"This collab link is read-only\");\n\t\treturn true;\n\t}\n\n\tconstructor(ctx: InteractiveModeContext) {\n\t\tthis.#ctx = ctx;\n\t}\n\n\tasync join(link: string): Promise<void> {\n\t\tconst parsed = parseCollabLink(link);\n\t\tif (\"error\" in parsed) throw new Error(parsed.error);\n\t\tthis.#roomId = parsed.roomId;\n\t\tthis.#writeToken = parsed.writeToken ? Buffer.from(parsed.writeToken).toString(\"base64url\") : undefined;\n\t\tconst key = await importRoomKey(parsed.key);\n\n\t\tthis.#returnSessionFile = this.#ctx.sessionManager.getSessionFile() ?? null;\n\n\t\tconst socket = new CollabSocket({ wsUrl: parsed.wsUrl, role: \"guest\", key });\n\t\tthis.#socket = socket;\n\n\t\tconst firstWelcome = Promise.withResolvers<void>();\n\t\tlet joined = false;\n\t\tthis.#joinReject = err => firstWelcome.reject(err);\n\n\t\tconst finishJoin = (): void => {\n\t\t\tif (joined) return;\n\t\t\tjoined = true;\n\t\t\tfirstWelcome.resolve();\n\t\t};","sourceCodeStart":238,"sourceCodeEnd":274,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/collab/guest.ts#L238-L274","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-copy the complete invite link from the host's output — verify it ends with a long base64url key segment after the roomId.","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.","Run parseCollabLink(link) first and surface its error to the user instead of a raw throw.","Use the web deep link variant if the plain link keeps getting corrupted by click-to-open."],"exampleFix":"// before\nawait guest.join(userInput); // throws opaque parsed.error\n// after\nconst parsed = parseCollabLink(userInput);\nif (\"error\" in parsed) {\n  ui.showError(\"Invalid collaboration link: \" + parsed.error);\n  return;\n}\nawait guest.join(userInput);","handlingStrategy":"try-catch","validationCode":"const parsed = parseCollabLink(link);\nif (\"error\" in parsed) {\n  report(`Invalid link: ${parsed.error}`);\n  return;\n}","typeGuard":"function isParsedCollabLink(p: ReturnType<typeof parseCollabLink>): p is ParsedCollabLink {\n  return !(\"error\" in p);\n}","tryCatchPattern":"try {\n  await guest.join(link);\n} catch (err) {\n  ui.showError(`Join failed: ${err instanceof Error ? err.message : String(err)}`);\n}","preventionTips":["Copy the full link in one action; avoid terminals/chat clients that wrap or rewrite it.","Sanitize pasted input: trim whitespace, restore %23 to #.","Prefer sharing web deep links which survive strict URL stacks."],"tags":["collaboration","input-validation","link-parsing"],"backgroundTag":"invalid-invite-link","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}