{"record":{"id":"148ff55405c07732","repo":"can1357/oh-my-pi","slug":"collab-weburl-must-start-with-http-or-https","errorCode":null,"errorMessage":"collab.webUrl must start with http:// or https://","messagePattern":"collab\\.webUrl must start with http:// or https://","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/collab/protocol.ts","lineNumber":221,"sourceCode":"\t\t: normalized.origin;\n\treturn `${compact}/r/${roomId}.${keyText}`;\n}\n\nfunction normalizeCollabWebBaseUrl(relayUrl: string, webUrl?: string): string {\n\tconst explicitWebUrl = webUrl?.trim();\n\tif (!explicitWebUrl) {\n\t\tconst normalized = normalizeRelayOrigin(relayUrl);\n\t\tif (\"error\" in normalized) throw new Error(normalized.error);\n\t\treturn normalized.origin.startsWith(\"wss://\")\n\t\t\t? `https://${normalized.origin.slice(\"wss://\".length)}`\n\t\t\t: `http://${normalized.origin.slice(\"ws://\".length)}`;\n\t}\n\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(explicitWebUrl);\n\t} catch {\n\t\tthrow new Error(\"collab.webUrl must start with http:// or https://\");\n\t}\n\tif (url.protocol !== \"http:\" && url.protocol !== \"https:\") {\n\t\tthrow new Error(\"collab.webUrl must start with http:// or https://\");\n\t}\n\tif (url.protocol === \"http:\" && !isLocalHostname(url.hostname)) {\n\t\tthrow new Error(\"collab.webUrl must use https:// unless it targets localhost\");\n\t}\n\tif (url.search || url.hash) {\n\t\tthrow new Error(\"collab.webUrl must not include a query string or fragment\");\n\t}\n\tconst path = url.pathname.replace(/\\/+$/, \"\");\n\treturn `${url.origin}${path}`;\n}\n\n/**\n * Render the browser deep link. The browser UI may be hosted separately from\n * the relay; the fragment always carries the relay-specific collab link, so\n * room secrets stay out of HTTP path and query bytes.","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/collab/protocol.ts#L203-L239","documentation":"normalizeCollabWebBaseUrl throws this when the explicitly configured webUrl cannot be parsed as a URL at all, or when its protocol is neither http: nor https:. The message is the same in both cases because a URL like 'collab.example.com' or 'wss://...' would fail here — the web base must be an HTTP(S) URL that browsers can open.","triggerScenarios":"Calling formatCollabWebLink with webUrl set to: a scheme-less host ('collab.example.com'), a ws/wss URL, an ftp:// URL, or any string new URL() rejects (spaces, stray characters, empty after trim).","commonSituations":"Config collab.webUrl copied from the relay's wss:// address instead of the web UI's https:// address; webUrl set to a bare hostname; trailing whitespace or quotes left in a config value making new URL throw.","solutions":["Set webUrl to a full URL with http:// or https:// scheme, e.g. https://collab.example.com.","Do not reuse the relay's ws:// or wss:// URL as webUrl; derive the https address of the web UI instead.","Trim the value and strip quotes before saving; verify with new URL(webUrl) in a quick script.","Omit webUrl entirely to derive it from the relay origin."],"exampleFix":"// before\nwebUrl: \"collab.example.com\"\n// after\nwebUrl: \"https://collab.example.com\"","handlingStrategy":"validation","validationCode":"function webUrlOk(s: string): boolean {\n  try { const u = new URL(s.trim()); return u.protocol === \"http:\" || u.protocol === \"https:\"; }\n  catch { return false; }\n}\nif (!webUrlOk(webUrl)) throw new Error(\"webUrl must be an absolute http(s) URL\");\nformatCollabWebLink(relayUrl, roomId, key, token, webUrl.trim());","typeGuard":null,"tryCatchPattern":"try {\n  const webLink = formatCollabWebLink(relayUrl, roomId, key, token, webUrl);\n} catch (err) {\n  ui.showError(`Invalid collab.webUrl: ${(err as Error).message}`);\n}","preventionTips":["Always include the http:// or https:// scheme in collab.webUrl.","Never reuse the relay's ws:// URL as the web base.","Trim config values and strip stray quotes before use."],"tags":["configuration","url-validation","collaboration"],"backgroundTag":"invalid-url-config","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}