{"record":{"id":"aa5f7edc7e962dc0","repo":"aaif-goose/goose","slug":"external-acp-backend-url-must-use-http-or-https","errorCode":null,"errorMessage":"External ACP backend URL must use http: or https:, got ${url.protocol}","messagePattern":"External ACP backend URL must use http: or https:, got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"ui/desktop/src/acp/url.ts","lineNumber":46,"sourceCode":"\nfunction isIpv4LoopbackLiteral(hostname: string): boolean {\n  const octets = hostname.split('.');\n  if (octets.length !== 4 || octets.some((octet) => !/^\\d+$/.test(octet))) {\n    return false;\n  }\n\n  return octets.every((octet) => Number(octet) <= 255) && Number(octets[0]) === 127;\n}\n\nexport function normalizeAcpHttpBaseUrl(rawBaseUrl: string): string {\n  const trimmed = rawBaseUrl.trim();\n  if (!trimmed) {\n    throw new Error('External ACP backend URL is required');\n  }\n\n  const url = new URL(trimmed);\n  if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n    throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`);\n  }\n\n  if (url.search || url.hash) {\n    throw new Error('External ACP backend URL must not include query parameters or fragments');\n  }\n\n  const pathname = url.pathname.replace(/\\/+$/, '');\n  if (pathname.endsWith('/acp')) {\n    throw new Error('External ACP backend URL must be the base URL before /acp');\n  }\n\n  return `${url.origin}${pathname}`;\n}\n\nfunction httpEndpointUrlFromHttpBase(rawBaseUrl: string, endpoint: 'status' | 'acp'): string {\n  const baseUrl = normalizeAcpHttpBaseUrl(rawBaseUrl);\n  const url = new URL(baseUrl);\n  url.pathname = `${url.pathname.replace(/\\/+$/, '')}/${endpoint}`;","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/aaif-goose/goose/blob/3810898a7447ec3299be72e223d3570a7aabf0ab/ui/desktop/src/acp/url.ts#L28-L64","documentation":"Thrown by normalizeAcpHttpBaseUrl when the input parses as a URL but its protocol is neither http: nor https:. Because the endpoint is used to build WebSocket/HTTP ACP endpoints, the code requires a plain web scheme and rejects anything else — most commonly ws:// or wss:// pasted by users who copied the WebSocket URL, or file:///ftp:// typos.","triggerScenarios":"normalizeAcpHttpBaseUrl('ws://127.0.0.1:8080/acp') — user copied the ws URL from logs; 'unix:///path' or 'file://...' entries; a URL missing the scheme entirely can also fail URL parsing (that throws a TypeError from new URL, not this message).","commonSituations":"Copy-pasting the ws:// URL that getAcpUrl returns elsewhere in the app; docs or tutorials showing the WebSocket endpoint; automation writing scheme-less host:port values.","solutions":["Use the http(s) base URL of the backend (e.g. http://127.0.0.1:8080) — the code appends /acp itself.","If the value starts with ws:// or wss://, convert the scheme to http:// or https:// and keep host/port.","Always include the scheme; host:port alone makes new URL throw a different error."],"exampleFix":"// before\nconst url = new URL(trimmed);\nif (url.protocol !== 'http:' && url.protocol !== 'https:') {\n  throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`);\n}\n\n// after (auto-correct the common ws paste, then validate)\nconst corrected = trimmed.replace(/^wss?:\\/\\//i, (m) => (m.toLowerCase() === 'ws://' ? 'http://' : 'https://'));\nconst url = new URL(corrected);\nif (url.protocol !== 'http:' && url.protocol !== 'https:') {\n  throw new Error(`External ACP backend URL must use http: or https:, got ${url.protocol}`);\n}","handlingStrategy":"validation","validationCode":"// Normalize the common ws:// paste, then check the scheme\nfunction toHttpBaseUrl(raw: string): string {\n  const value = raw.trim().replace(/^ws:\\/\\//i, 'http://').replace(/^wss:\\/\\//i, 'https://');\n  const { protocol } = new URL(value);\n  if (protocol !== 'https:' && protocol !== 'http:') {\n    throw new Error(`Unsupported scheme ${protocol}; use http:// or https://`);\n  }\n  return value;\n}","typeGuard":"function isHttpUrl(value: string): boolean {\n  try {\n    const { protocol } = new URL(value.trim());\n    return protocol === 'http:' || protocol === 'https:';\n  } catch {\n    return false;\n  }\n}","tryCatchPattern":"try {\n  const base = normalizeAcpHttpBaseUrl(inputUrl);\n} catch (error) {\n  if (/must use http: or https:/.test(String(error))) {\n    setFieldError('backendUrl', 'Use http:// or https:// — the /acp websocket path is added automatically');\n    return;\n  }\n  throw error;\n}","preventionTips":["Paste the plain http(s) origin of the backend, never a ws:// endpoint.","Validate with isHttpUrl on input change to give inline feedback.","Document 'base URL' in the field's placeholder text."],"tags":["validation","url","scheme","configuration"],"backgroundTag":null,"analyzedSha":"3810898a7447ec3299be72e223d3570a7aabf0ab","analyzedAt":"2026-08-16T10:14:26.282Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}