{"record":{"id":"3d6b80dcbca8ec97","repo":"can1357/oh-my-pi","slug":"collab-weburl-must-not-include-a-query-string-or-f","errorCode":null,"errorMessage":"collab.webUrl must not include a query string or fragment","messagePattern":"collab\\.webUrl must not include a query string or fragment","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/collab/protocol.ts","lineNumber":230,"sourceCode":"\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.\n */\nexport function formatCollabWebLink(\n\trelayUrl: string,\n\troomId: string,\n\tkey: Uint8Array,\n\twriteToken?: Uint8Array,\n\twebUrl?: string,\n): string {\n\treturn `${normalizeCollabWebBaseUrl(relayUrl, webUrl)}/#${formatCollabLink(relayUrl, roomId, key, writeToken)}`;","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/collab/protocol.ts#L212-L248","documentation":"normalizeCollabWebBaseUrl validates the collab.webUrl config value used to build web links for collaboration sessions. The library requires a clean base URL: any query string ('?...') or fragment ('#...') makes it impossible to deterministically append session paths, so it throws. It also enforces http(s) scheme and https-for-non-localhost, but this specific error is the query/fragment check.","triggerScenarios":"Setting collab.webUrl in config to a URL like 'https://collab.example.com/?org=acme' or 'https://collab.example.com/#/dashboard'; formatCollabWebLink calls normalizeCollabWebBaseUrl on every link render, so any session that renders a collab web link fails until the config is fixed.","commonSituations":"Copying a full page URL from a browser address bar (which often carries ?token= or #/ routes) and pasting it into collab.webUrl; including tracking parameters appended by a web app.","solutions":["Edit the collab.webUrl config value and strip everything from '?' and '#' onward, keeping only scheme://host[:port]/path","Verify with a quick check: new URL(value).search === '' && new URL(value).hash === ''","If auth/query data is needed, pass it via environment or a dedicated config key, not the base URL"],"exampleFix":"// before\ncollab.webUrl = \"https://collab.example.com/?org=acme#/board\"\n// after\ncollab.webUrl = \"https://collab.example.com\"","handlingStrategy":"validation","validationCode":"function isValidCollabWebUrl(v) {\n  try {\n    const u = new URL(v);\n    return (u.protocol === 'https:' || (u.protocol === 'http:' && /^(localhost|127\\.|\\[::1\\])/.test(u.hostname))) && !u.search && !u.hash;\n  } catch { return false; }\n}\n// call before assigning collab.webUrl","typeGuard":"function isCleanHttpUrl(v) {\n  if (typeof v !== 'string') return false;\n  try {\n    const u = new URL(v);\n    return (u.protocol === 'https:' || u.protocol === 'http:') && u.search === '' && u.hash === '';\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const base = normalizeCollabWebBaseUrl(config.collab.webUrl);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('collab.webUrl')) {\n    logger.warn('collab.webUrl invalid, using default', { value: config.collab.webUrl });\n    return DEFAULT_COLLAB_WEB_URL;\n  }\n  throw err;\n}","preventionTips":["Paste only scheme://host/path into collab.webUrl — never copy the browser address bar verbatim","Validate config at load time with the URL checks before the app starts","Keep auth/query params in dedicated config keys, not the base URL"],"tags":["config","validation","url"],"backgroundTag":"invalid-config-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}